/**
 * layui.table
 * 表格组件
 */
import { layui } from '../core/layui.js';
import { lay } from '../core/lay.js';
import { i18n } from '../core/i18n.js';
import $ from 'jquery';
import { laytpl } from '../core/laytpl.js';
import { laypage } from './laypage.js';
import { util } from './util.js';
import { layer } from './layer.js';
import { form } from './form.js';

var hint = layui.hint();
var device = layui.device();

// api
var table = {
  config: {
    // 全局配置项
    checkName: 'LAY_CHECKED', // 是否选中状态的特定字段名
    indexName: 'LAY_INDEX', // 下标索引
    initIndexName: 'LAY_INDEX_INIT', // 初始下标索引名，仅用于内部恢复当前页表格排序
    numbersName: 'LAY_NUM', // 序号
    disabledName: 'LAY_DISABLED', // 禁用状态的特定字段名
  },
  cache: {}, // 数据缓存

  // 设置全局项
  set: function (options) {
    var that = this;
    that.config = $.extend({}, that.config, options);
    return that;
  },

  // 事件
  on: function (events, callback) {
    return layui.onevent.call(this, MOD_NAME, events, callback);
  },
};

// 操作当前实例
var thisTable = function () {
  var that = this;
  var options = that.config;
  var id = options.id || options.index;

  return {
    config: options,
    reload: function (options, deep) {
      that.reload.call(that, options, deep);
    },
    reloadData: function (options, deep) {
      table.reloadData(id, options, deep);
    },
    setColsWidth: function () {
      that.setColsWidth.call(that);
    },
    resize: function () {
      // 重置表格尺寸/结构
      that.resize.call(that);
    },
  };
};

// 获取当前实例
var getThisTable = function (id) {
  var that = thisTable.that[id];
  if (!that)
    hint.error(
      id
        ? "The table instance with ID '" + id + "' not found"
        : 'ID argument required',
    );
  return that || null;
};

// 获取当前实例配置项
var getThisTableConfig = function (id) {
  var config = thisTable.config[id];
  if (!config)
    hint.error(
      id
        ? "The table instance with ID '" + id + "' not found"
        : 'ID argument required',
    );
  return config || null;
};

// lay 函数可以处理 Selector，HTMLElement，JQuery 类型
// 无效的 CSS 选择器字符串，会抛出 SyntaxError 异常，此时直接返回 laytpl 模板字符串
var resolveTplStr = function (templet) {
  try {
    return lay(templet).html();
  } catch {
    return templet;
  }
};

// 解析自定义模板数据
var parseTempData = function (obj) {
  obj = obj || {};

  var options = this.config || {};
  var item3 = obj.item3; // 表头数据
  var content = obj.content; // 原始内容
  if (item3.type === 'numbers') content = obj.tplData[table.config.numbersName];

  // 是否编码 HTML
  var escaped = 'escape' in item3 ? item3.escape : options.escape;
  if (escaped) content = util.escape(content);

  // 获取模板
  var templet =
    (obj.text && item3.exportTemplet) || item3.templet || item3.toolbar;

  // 获取模板内容
  if (templet) {
    content =
      typeof templet === 'function'
        ? templet.call(item3, obj.tplData, obj.obj)
        : laytpl(resolveTplStr(templet) || String(content)).render(
            $.extend(
              {
                LAY_COL: item3,
              },
              obj.tplData,
            ),
          );
  }

  // 是否只返回文本
  return obj.text ? $('<div>' + content + '</div>').text() : content;
};

// 字符
var MOD_NAME = 'table';
var MOD_ID = 'lay-' + MOD_NAME + '-id';
var ELEM = '.layui-table';
// var THIS = 'layui-this';
// var SHOW = 'layui-show';
var HIDE = 'layui-hide';
var HIDE_V = 'layui-hide-v';
// var DISABLED = 'layui-disabled';
var NONE = 'layui-none';

var ELEM_VIEW = 'layui-table-view';
var ELEM_TOOL = '.layui-table-tool';
var ELEM_BOX = '.layui-table-box';
var ELEM_INIT = '.layui-table-init';
var ELEM_HEADER = '.layui-table-header';
var ELEM_BODY = '.layui-table-body';
var ELEM_MAIN = '.layui-table-main';
var ELEM_FIXED = '.layui-table-fixed';
var ELEM_FIXL = '.layui-table-fixed-l';
var ELEM_FIXR = '.layui-table-fixed-r';
var ELEM_TOTAL = '.layui-table-total';
var ELEM_PAGE = '.layui-table-page';
var ELEM_PAGE_VIEW = '.layui-table-pageview';
var ELEM_SORT = '.layui-table-sort';
var ELEM_CHECKED = 'layui-table-checked';
var ELEM_EDIT = 'layui-table-edit';
var ELEM_HOVER = 'layui-table-hover';
var ELEM_GROUP = 'laytable-cell-group';
var ELEM_COL_SPECIAL = 'layui-table-col-special';
var ELEM_TOOL_PANEL = 'layui-table-tool-panel';
var ELEM_EXPAND = 'layui-table-expanded';
var DISABLED_TRANSITION = 'layui-table-disabled-transition';
var FIXED_HEIGHT_PATCH = 'layui-table-fixed-height-patch';

var DATA_MOVE_NAME = 'LAY_TABLE_MOVE_DICT';

var resizeObserver = lay.createSharedResizeObserver(MOD_NAME);

// thead 区域模板
var TPL_HEADER = function (options) {
  var rowCols =
    '{{#var colspan = layui.type(item2.colspan2) === \'number\' ? item2.colspan2 : item2.colspan; if(colspan){}} colspan="{{=colspan}}"{{#} if(item2.rowspan){}} rowspan="{{=item2.rowspan}}"{{#}}}';

  options = options || {};
  return [
    '<table cellspacing="0" cellpadding="0" border="0" class="layui-table" ',
    '{{# if(d.data.skin){ }}lay-skin="{{=d.data.skin}}"{{# } }} {{# if(d.data.size){ }}lay-size="{{=d.data.size}}"{{# } }} {{# if(d.data.even){ }}lay-even{{# } }}>',
    '<thead>',
    '{{# layui.each(d.data.cols, function(i1, item1){ }}',
    '<tr>',
    '{{# layui.each(item1, function(i2, item2){ }}',
    '{{# if(item2.fixed && item2.fixed !== "right"){ left = true; } }}',
    '{{# if(item2.fixed === "right"){ right = true; } }}',
    (function () {
      if (options.fixed && options.fixed !== 'right') {
        return '{{# if(item2.fixed && item2.fixed !== "right"){ }}';
      }
      if (options.fixed === 'right') {
        return '{{# if(item2.fixed === "right"){ }}';
      }
      return '';
    })(),
    '{{# var isSort = !(item2.colGroup) && item2.sort; }}',
    '<th data-field="{{= item2.field||i2 }}" data-key="{{=d.index}}-{{=i1}}-{{=i2}}" {{# if( item2.parentKey){ }}data-parentkey="{{= item2.parentKey }}"{{# } }} {{# if(item2.minWidth){ }}data-minwidth="{{=item2.minWidth}}"{{# } }} {{# if(item2.maxWidth){ }}data-maxwidth="{{=item2.maxWidth}}"{{# } }} {{# if(item2.style){ }}style="{{=item2.style}}"{{# } }} ' +
      rowCols +
      ' {{# if(item2.unresize || item2.colGroup){ }}data-unresize="true"{{# } }} class="{{# if(item2.hide){ }}layui-hide{{# } }}{{# if(isSort){ }} layui-unselect{{# } }}{{# if(!item2.field){ }} layui-table-col-special{{# } }}"{{# if(item2.title){ }} title="{{ layui.$(\'<div>\' + item2.title + \'</div>\').text() }}"{{# } }}>',
    '<div class="layui-table-cell laytable-cell-',
    '{{# if(item2.colGroup){ }}',
    'group',
    '{{# } else { }}',
    '{{=d.index}}-{{=i1}}-{{=i2}}',
    '{{# if(item2.type !== "normal"){ }}',
    ' laytable-cell-{{= item2.type }}',
    '{{# } }}',
    '{{# } }}',
    '" {{#if(item2.align){}}align="{{=item2.align}}"{{#}}}>',
    '{{# if(item2.type === "checkbox"){ }}', //复选框
    '<input type="checkbox" name="layTableCheckbox" lay-skin="primary" lay-filter="layTableAllChoose" {{# if(item2[d.data.checkName]){ }}checked{{# }; }}>',
    '{{# } else { }}',
    '<span>{{-item2.title||""}}</span>',
    '{{# if(isSort){ }}',
    '<span class="layui-table-sort layui-inline"><i class="layui-edge layui-table-sort-asc" title="{{= d.i18nMessages.table_sort_asc }}"></i><i class="layui-edge layui-table-sort-desc" title="{{= d.i18nMessages.table_sort_desc }}"></i></span>',
    '{{# } }}',
    '{{# } }}',
    '</div>',
    '</th>',
    options.fixed ? '{{# }; }}' : '',
    '{{# }); }}',
    '</tr>',
    '{{# }); }}',
    '</thead>',
    '</table>',
  ].join('');
};

// tbody 区域模板
var TPL_BODY = [
  '<table cellspacing="0" cellpadding="0" border="0" class="layui-table" ',
  '{{# if(d.data.skin){ }}lay-skin="{{=d.data.skin}}"{{# } }} {{# if(d.data.size){ }}lay-size="{{=d.data.size}}"{{# } }} {{# if(d.data.even){ }}lay-even{{# } }}>',
  '<tbody></tbody>',
  '</table>',
].join('');

// 主模板
var TPL_MAIN = [
  '{{# if(d.data.toolbar){ }}',
  '<div class="layui-table-tool">',
  '<div class="layui-table-tool-temp"></div>',
  '<div class="layui-table-tool-self"></div>',
  '</div>',
  '{{# } }}',
  '<div class="layui-table-box">',
  '{{# if(d.data.loading){ }}',
  '<div class="layui-table-init">',
  '<div class="layui-table-loading-icon">',
  '{{# if(typeof d.data.loading === "string"){ }}',
  '{{- d.data.loading}}',
  '{{# } else{ }}',
  '<i class="layui-icon layui-icon-loading layui-anim layui-anim-rotate layui-anim-loop"></i>',
  '{{# } }}',
  '</div>',
  '</div>',
  '{{# } }}',
  '{{# var left, right; }}',
  '<div class="layui-table-header">',
  TPL_HEADER(),
  '</div>',
  '<div class="layui-table-body layui-table-main">',
  TPL_BODY,
  '</div>',
  '{{# if(left){ }}',
  '<div class="layui-table-fixed layui-table-fixed-l">',
  '<div class="layui-table-header">',
  TPL_HEADER({ fixed: true }),
  '</div>',
  '<div class="layui-table-body">',
  TPL_BODY,
  '</div>',
  '</div>',
  '{{# }; }}',
  '{{# if(right){ }}',
  '<div class="layui-table-fixed layui-table-fixed-r layui-hide">',
  '<div class="layui-table-header">',
  TPL_HEADER({ fixed: 'right' }),
  '<div class="layui-table-mend"></div>',
  '</div>',
  '<div class="layui-table-body">',
  TPL_BODY,
  '</div>',
  '</div>',
  '{{# }; }}',
  '</div>',
  '{{# if(d.data.totalRow){ }}',
  '<div class="layui-table-total">',
  '<table cellspacing="0" cellpadding="0" border="0" class="layui-table" ',
  '{{# if(d.data.skin){ }}lay-skin="{{=d.data.skin}}"{{# } }} {{# if(d.data.size){ }}lay-size="{{=d.data.size}}"{{# } }} {{# if(d.data.even){ }}lay-even{{# } }}>',
  '<tbody><tr><td><div class="layui-table-cell" style="visibility: hidden;">Total</div></td></tr></tbody>',
  '</table>',
  '</div>',
  '{{# } }}',
  '<div class="layui-table-column layui-table-page layui-hide">',
  '<div class="layui-inline layui-table-pageview" id="layui-table-page{{=d.index}}"></div>',
  '</div>',
].join('');

var _WIN = $(window);
var _DOC = $(document);

// constructor
var Class = function (options) {
  var that = this;
  that.index = table.index = lay.autoIncrementer('table');
  that.config = $.extend({}, that.config, table.config, options);
  that.unobserveResize = $.noop;
  that.render();
};

// 初始默认配置
Class.prototype.config = {
  limit: 10, // 每页显示的数量
  loading: true, // 请求数据时，是否显示 loading
  escape: true, // 是否开启 HTML 编码功能，即转义 html 原文
  cellMinWidth: 60, // 所有单元格默认最小宽度
  cellMaxWidth: Number.MAX_VALUE, // 所有单元格默认最大宽度
  editTrigger: 'click', // 单元格编辑的事件触发方式
  defaultToolbar: ['filter', 'exports', 'print'], // 工具栏右侧图标
  defaultContextmenu: true, // 显示默认上下文菜单
  autoSort: true, // 是否前端自动排序。如果否，则需自主排序（通常为服务端处理好排序）
  cols: [],
};

// 表格渲染
Class.prototype.render = function (type) {
  var that = this;
  var options = that.config;

  options.elem = $(options.elem);
  options.where = options.where || {};

  // 初始化 id 属性 - 优先取 options > 元素 id > 自增索引
  var id = (options.id =
    'id' in options ? options.id : options.elem.attr('id') || that.index);

  // 重复 render 时清理旧实例
  if (thisTable.that[id] && thisTable.that[id] !== that) {
    thisTable.that[id].dispose();
  }

  thisTable.that[id] = that; // 记录当前实例对象
  thisTable.config[id] = options; // 记录当前实例配置项

  // 请求参数的自定义格式
  options.request = $.extend(
    {
      pageName: 'page',
      limitName: 'limit',
    },
    options.request,
  );

  // 响应数据的自定义格式
  options.response = $.extend(
    {
      statusName: 'code', //规定数据状态的字段名称
      statusCode: 0, //规定成功的状态码
      msgName: 'msg', //规定状态信息的字段名称
      dataName: 'data', //规定数据总数的字段名称
      totalRowName: 'totalRow', //规定数据统计的字段名称
      countName: 'count',
    },
    options.response,
  );

  // 如果 page 传入 laypage 对象
  if (options.page !== null && typeof options.page === 'object') {
    options.limit = options.page.limit || options.limit;
    options.limits = options.page.limits || options.limits;
    that.page = options.page.curr = options.page.curr || 1;
    delete options.page.elem;
    delete options.page.jump;
  }

  // 加载 i18n 自定义文本
  options.text = $.extend(
    true,
    {
      none: i18n.$t('table.noData'),
    },
    options.text,
  );

  if (!options.elem[0]) return that;

  // 若元素未设 lay-filter 属性，则取实例 id 值
  if (!options.elem.attr('lay-filter')) {
    options.elem.attr('lay-filter', options.id);
  }

  // 仅重载数据
  if (type === 'reloadData') {
    // 请求数据
    return that.pullData(that.page, {
      type: 'reloadData',
    });
  }

  // 初始化索引
  options.index = that.index;
  that.key = options.id || options.index;

  // 初始化一些其他参数
  that.setInit();

  // 高度铺满：full-差距值
  if (options.height && /^full-.+$/.test(options.height)) {
    that.fullHeightGap = options.height.split('-')[1];
    options.height = _WIN.height() - (parseFloat(that.fullHeightGap) || 0);
  } else if (options.height && /^#\w+\S*-.+$/.test(options.height)) {
    var parentDiv = options.height.split('-');
    that.parentHeightGap = parentDiv.pop();
    that.parentDiv = parentDiv.join('-');
    options.height =
      $(that.parentDiv).height() - (parseFloat(that.parentHeightGap) || 0);
  } else if (typeof options.height === 'function') {
    that.customHeightFunc = options.height;
    options.height = that.customHeightFunc();
  }

  // 开始插入替代元素
  var othis = options.elem;
  var hasRender = othis.next('.' + ELEM_VIEW);

  // 主容器
  var reElem = (that.elem = $('<div></div>'));

  // 添加 className
  reElem
    .addClass(
      (function () {
        var arr = [
          ELEM_VIEW,
          ELEM_VIEW + '-' + that.index,
          'layui-form',
          'layui-border-box',
        ];
        if (options.className) arr.push(options.className);
        return arr.join(' ');
      })(),
    )
    .attr(
      (function () {
        var obj = {
          'lay-filter': 'LAY-TABLE-FORM-DF-' + that.index,
          style: (function () {
            var arr = [];
            if (options.width) arr.push('width:' + options.width + 'px;');
            // if(options.height) arr.push('height:'+ options.height + 'px;');
            return arr.join('');
          })(),
        };
        obj[MOD_ID] = options.id;
        return obj;
      })(),
    )
    .html(
      laytpl(TPL_MAIN, {
        open: '{{', // 标签符前缀
        close: '}}', // 标签符后缀
        tagStyle: 'legacy',
      }).render({
        data: options,
        index: that.index, //索引
        i18nMessages: {
          table_sort_asc: i18n.$t('table.sort.asc'),
          table_sort_desc: i18n.$t('table.sort.desc'),
        },
      }),
    );

  // 初始化样式
  that.renderStyle();

  // 生成替代元素
  hasRender[0] && hasRender.remove(); // 如果已经渲染，则 Rerender
  othis.after(reElem);

  // 各级容器
  that.layTool = reElem.find(ELEM_TOOL);
  that.layBox = reElem.find(ELEM_BOX);
  that.layHeader = reElem.find(ELEM_HEADER);
  that.layMain = reElem.find(ELEM_MAIN);
  that.layBody = reElem.find(ELEM_BODY);
  that.layFixed = reElem.find(ELEM_FIXED);
  that.layFixLeft = reElem.find(ELEM_FIXL);
  that.layFixRight = reElem.find(ELEM_FIXR);
  that.layTotal = reElem.find(ELEM_TOTAL);
  that.layPage = reElem.find(ELEM_PAGE);

  // 初始化头部工具栏
  that.renderToolbar();

  // 初始化底部分页栏
  that.renderPagebar();

  // 让表格平铺
  that.fullSize();
  that.setColsWidth({ isInit: true });

  // 请求数据
  that.pullData(that.page, {
    done: function () {
      that.observeResize(); // 观察尺寸变化
    },
  });

  that.events(); // 事件
};

// 根据列类型，定制化参数
Class.prototype.initOpts = function (item) {
  // var that = this;
  // var options = that.config;
  var initWidth = {
    checkbox: 50,
    radio: 50,
    space: 30,
    numbers: 60,
  };

  // 让 type 参数兼容旧版本
  if (item.checkbox) item.type = 'checkbox';
  if (item.space) item.type = 'space';
  if (!item.type) item.type = 'normal';

  if (item.type !== 'normal') {
    item.unresize = true;
    item.width = item.width || initWidth[item.type];
  }
};

//初始化一些参数
Class.prototype.setInit = function (type) {
  var that = this;
  var options = that.config;

  options.clientWidth =
    options.width ||
    (function () {
      //获取容器宽度
      //如果父元素宽度为0（一般为隐藏元素），则继续查找上层元素，直到找到真实宽度为止
      var getWidth = function (parent) {
        var width;
        var isNone;
        parent = parent || options.elem.parent();

        width = that.getContentWidth(parent);

        try {
          isNone = parent.css('display') === 'none';
        } catch {
          // ignore
        }
        var parentElem = parent.parent();
        if (parent[0] && parentElem && parentElem[0] && (!width || isNone))
          return getWidth(parentElem);
        return width;
      };
      return getWidth();
    })();

  if (type === 'width') return options.clientWidth;
  // 初始化高度配置，如果设置了最高高度，以最高高度形式为准
  options.height = options.maxHeight || options.height;

  // 初始化 css 参数
  if (options.css && options.css.indexOf(ELEM_VIEW) === -1) {
    var css = options.css.split('}');
    layui.each(css, function (index, value) {
      if (value) {
        css[index] = '.' + ELEM_VIEW + '-' + that.index + ' ' + value;
      }
    });
    options.css = css.join('}');
  }

  // 封装对 col 的配置处理
  var initChildCols = function (i1, item1, i2, item2) {
    //如果列参数为空，则移除
    if (!item2) {
      item1.splice(i2, 1);
      return;
    }

    item2.key = [options.index, i1, i2].join('-');
    item2.colspan = item2.colspan || 0;
    item2.rowspan = item2.rowspan || 0;

    //根据列类型，定制化参数
    that.initOpts(item2);

    //设置列的父列索引
    //如果是组合列，则捕获对应的子列
    var indexChild = i1 + (parseInt(item2.rowspan) || 1);
    if (indexChild < options.cols.length) {
      // 只要不是最后一层都会有子列
      item2.colGroup = true;
      var childIndex = 0;
      layui.each(options.cols[indexChild], function (i22, item22) {
        //如果子列已经被标注为{HAS_PARENT}，或者子列累计 colspan 数等于父列定义的 colspan，则跳出当前子列循环
        if (
          item22.HAS_PARENT ||
          (childIndex >= 1 && childIndex == (item2.colspan || 1))
        )
          return;

        item22.HAS_PARENT = true;
        item22.parentKey = [options.index, i1, i2].join('-'); // i1 + '-' + i2;
        childIndex =
          childIndex + parseInt(item22.colspan > 1 ? item22.colspan : 1);
        initChildCols(indexChild, options.cols[indexChild], i22, item22);
      });
    } else {
      item2.colGroup = false;
    }
    item2.hide = (item2.hide && !item2.colGroup) || false; // 初始化中中间节点的hide信息不做处理，否则会出错，如果需要必须将其子节点也都同步成hide
  };

  // 初始化列参数
  layui.each(options.cols, function (i1, item1) {
    layui.each(item1, function (i2, item2) {
      if (i1) {
        delete item2.HAS_PARENT; // 去掉临时的计数排除标识，避免有新字段插入的时候重新计算被跳过导致下标出错的问题
      } else {
        initChildCols(i1, item1, i2, item2); // 只解析顶层节点由递归完成解析
      }
    });
  });
};

// 初始化样式
Class.prototype.renderStyle = function () {
  var that = this;
  var options = that.config;
  var index = that.index;
  var text = [];

  // 单元格宽度
  layui.each(options.cols, function (i1, item1) {
    layui.each(item1, function (i2, item2) {
      var key = [index, i1, i2].join('-');
      var val = ['width: ', item2.width || options.cellMinWidth, 'px'].join('');
      text.push('.laytable-cell-' + key + '{' + val + '}');
    });
  });

  // 自定义行样式
  (function (lineStyle) {
    if (!lineStyle) return;
    var trClassName =
      '.layui-table-view-' + index + ' .layui-table-body .layui-table tr';
    var rules = lineStyle.split(';');
    var cellMaxHeight = 'none';

    // 计算单元格最大高度
    layui.each(rules, function (i, rule) {
      rule = rule.split(':');
      if (rule[0] === 'height') {
        var val = parseFloat(rule[1]);
        if (!isNaN(val)) cellMaxHeight = val - 1 + 'px';
        return true;
      }
    });

    // 多行相关样式
    layui.each(
      [
        '{' + lineStyle + '}',
        '.layui-table-cell{height: auto; max-height: ' +
          cellMaxHeight +
          '; white-space: normal; text-overflow: clip;}',
        '> td:hover > .layui-table-cell{overflow: auto;}',
      ].concat(
        device.ie
          ? [
              '.layui-table-edit{height: ' + cellMaxHeight + ';}',
              'td[data-edit]:hover:after{height: ' + cellMaxHeight + ';}',
            ]
          : [],
      ),
      function (i, val) {
        val && text.push(trClassName + ' ' + val);
      },
    );
  })(options.lineStyle);

  // 自定义 css 属性
  if (options.css) text.push(options.css);
  text.push('.' + FIXED_HEIGHT_PATCH + '{height:auto;}');

  // 生成 style
  lay.style({
    target: that.elem[0],
    text: text.join(''),
    id: 'DF-table-' + index,
  });
};

// 初始头部工具栏
Class.prototype.renderToolbar = function () {
  var that = this;
  var options = that.config;
  var filter = options.elem.attr('lay-filter');

  // 添加工具栏左侧模板
  var leftDefaultTemp = [
    '<div class="layui-inline" lay-event="add"><i class="layui-icon layui-icon-add-1"></i></div>',
    '<div class="layui-inline" lay-event="update"><i class="layui-icon layui-icon-edit"></i></div>',
    '<div class="layui-inline" lay-event="delete"><i class="layui-icon layui-icon-delete"></i></div>',
  ].join('');
  var elemToolTemp = that.layTool.find('.layui-table-tool-temp');

  if (options.toolbar === 'default') {
    elemToolTemp.html(leftDefaultTemp);
  } else if (typeof options.toolbar === 'string') {
    var toolbarHtml = $(options.toolbar).html() || '';
    toolbarHtml && elemToolTemp.html(laytpl(toolbarHtml).render(options));
  }

  // 头部工具栏右上角默认工具
  var defaultConfig = {
    filter: {
      title: i18n.$t('table.tools.filter.title'),
      layEvent: 'LAYTABLE_COLS',
      icon: 'layui-icon-cols',
      onClick: function (obj) {
        var options = obj.config;
        var openPanel = obj.openPanel;

        openPanel({
          list: (function () {
            var lis = [];
            that.eachCols(function (i, item) {
              if (item.field && item.type == 'normal') {
                lis.push(
                  '<li><input type="checkbox" name="' +
                    item.field +
                    '" data-key="' +
                    item.key +
                    '" data-parentkey="' +
                    (item.parentKey || '') +
                    '" lay-skin="primary" ' +
                    (item.hide ? '' : 'checked') +
                    ' title="' +
                    util.escape(
                      $(
                        '<div>' +
                          (item.fieldTitle || item.title || item.field) +
                          '</div>',
                      ).text(),
                    ) +
                    '" lay-filter="LAY_TABLE_TOOL_COLS"></li>',
                );
              }
            });
            return lis.join('');
          })(),
          done: function () {
            form.on('checkbox(LAY_TABLE_TOOL_COLS)', function (obj) {
              var othis = $(obj.elem);
              var checked = this.checked;
              var key = othis.data('key');
              var col = that.col(key);
              var hide = col.hide;
              var parentKey = othis.data('parentkey');

              if (!col.key) return;

              // 同步勾选列的 hide 值和隐藏样式
              col.hide = !checked;
              that.elem
                .find('*[data-key="' + key + '"]')
                [checked ? 'removeClass' : 'addClass'](HIDE);

              // 根据列的显示隐藏，同步多级表头的父级相关属性值
              if (hide != col.hide) {
                that.setParentCol(!checked, parentKey);
              }

              // 重新适配尺寸
              that.resize();

              // 列筛选（显示或隐藏）后的事件
              layui.event.call(this, MOD_NAME, 'colToggled(' + filter + ')', {
                col: col,
                config: options,
              });
            });
          },
        });
      },
    },
    exports: {
      title: i18n.$t('table.tools.export.title'),
      layEvent: 'LAYTABLE_EXPORT',
      icon: 'layui-icon-export',
      onClick: function (obj) {
        // 自带导出
        var data = obj.data;
        var options = obj.config;
        var openPanel = obj.openPanel;
        var elem = obj.elem;

        if (!data.length)
          return layer.tips(i18n.$t('table.tools.export.noDataPrompt'), elem, {
            tips: 3,
          });
        if (device.ie) {
          layer.tips(i18n.$t('table.tools.export.compatPrompt'), elem, {
            tips: 3,
          });
        } else {
          openPanel({
            list: (function () {
              return [
                '<li data-type="csv">' +
                  i18n.$t('table.tools.export.csvText') +
                  '</li>',
              ].join('');
            })(),
            done: function (panel, list) {
              list.on('click', function () {
                var type = $(this).data('type');
                table.exportFile.call(that, options.id, null, type);
              });
            },
          });
        }
      },
    },
    print: {
      title: i18n.$t('table.tools.print.title'),
      layEvent: 'LAYTABLE_PRINT',
      icon: 'layui-icon-print',
      onClick: function (obj) {
        var data = obj.data;
        // var options = obj.config;
        var elem = obj.elem;

        if (!data.length)
          return layer.tips(i18n.$t('table.tools.print.noDataPrompt'), elem, {
            tips: 3,
          });
        var printWin = window.open('about:blank', '_blank');
        var style = [
          '<style>',
          'body{font-size: 12px; color: #5F5F5F;}',
          'table{width: 100%; border-collapse: collapse; border-spacing: 0;}',
          'th,td{line-height: 20px; padding: 9px 15px; border: 1px solid #ccc; text-align: left; font-size: 12px; color: #5F5F5F;}',
          'a{color: #5F5F5F; text-decoration:none;}',
          'img{max-height: 100%;}',
          '*.layui-hide{display: none}',
          '</style>',
        ].join('');
        var html = $(that.layHeader.html()); // 输出表头

        html.append(that.layMain.find('table').html()); // 输出表体
        html.append(that.layTotal.find('table').html()); // 输出合计行

        html.find('th.layui-table-patch').remove(); // 移除补丁
        // 移除表头特殊列
        html
          .find('thead>tr>th.' + ELEM_COL_SPECIAL)
          .filter(function (i, thElem) {
            return !$(thElem).children('.' + ELEM_GROUP).length; // 父级表头除外
          })
          .remove();
        html.find('tbody>tr>td.' + ELEM_COL_SPECIAL).remove(); // 移除表体特殊列

        printWin.document.write(style + html.prop('outerHTML'));
        printWin.document.close();

        if (layui.device('edg').edg) {
          printWin.onafterprint = printWin.close;
          printWin.print();
        } else {
          printWin.print();
          printWin.close();
        }
      },
    },
  };

  // 若开启 defaultToolbar
  if (typeof options.defaultToolbar === 'object') {
    var iconElem = [];
    options.defaultToolbar = $.map(options.defaultToolbar, function (item) {
      var itemIsName = typeof item === 'string';
      var thisItem = itemIsName ? defaultConfig[item] : item;
      if (thisItem) {
        // 根据 name 匹配默认工具并合并
        if (thisItem.name && defaultConfig[thisItem.name]) {
          thisItem = $.extend({}, defaultConfig[thisItem.name], thisItem);
        }
        // 初始化默认工具 name
        if (!thisItem.name && itemIsName) {
          thisItem.name = item;
        }
        // 图标列表
        iconElem.push(
          '<div class="layui-inline" title="' +
            thisItem.title +
            '" lay-event="' +
            thisItem.layEvent +
            '">' +
            '<i class="layui-icon ' +
            thisItem.icon +
            '"></i>' +
            '</div>',
        );
      }
      return thisItem;
    });
    that.layTool.find('.layui-table-tool-self').html(iconElem.join(''));
  }
};

// 分页栏
Class.prototype.renderPagebar = function () {
  var that = this;
  var options = that.config;

  var layPagebar = (that.layPagebar = $(
    '<div class="layui-inline layui-table-pagebar"></div>',
  ));

  // 开启分页栏自定义模板
  if (options.pagebar) {
    var pagebarHtml = $(options.pagebar).html() || '';
    pagebarHtml && layPagebar.append(laytpl(pagebarHtml).render(options));
    that.layPage.append(layPagebar);
  }
};

// 同步表头父列的相关值
Class.prototype.setParentCol = function (hide, parentKey) {
  var that = this;
  var options = that.config;

  var parentTh = that.layHeader.find('th[data-key="' + parentKey + '"]'); // 获取父列元素
  var parentColspan = parseInt(parentTh.attr('colspan')) || 0;

  if (parentTh[0]) {
    var arrParentKey = parentKey.split('-');
    var getThisCol = options.cols[arrParentKey[1]][arrParentKey[2]];

    hide ? parentColspan-- : parentColspan++;

    parentTh.attr('colspan', parentColspan);
    parentTh[parentColspan ? 'removeClass' : 'addClass'](HIDE); // 如果子列显示，父列必然需要显示

    getThisCol.colspan2 = parentColspan; // 更新实际的 colspan 数
    getThisCol.hide = parentColspan < 1; // 同步 hide 参数

    // 递归，继续往上查询是否有父列
    var nextParentKey = parentTh.data('parentkey');
    nextParentKey && that.setParentCol(hide, nextParentKey);
  }
};

// 多级表头补丁
Class.prototype.setColsPatch = function () {
  var that = this;
  var options = that.config;

  // 同步表头父列的相关值
  layui.each(options.cols, function (i1, item1) {
    layui.each(item1, function (i2, item2) {
      if (item2.hide) {
        that.setParentCol(item2.hide, item2.parentKey);
      }
    });
  });
};

// 设置组合表头的最大宽度
Class.prototype.setGroupWidth = function (th) {
  var that = this;
  var options = that.config;

  if (options.cols.length <= 1) return;

  // 获取表头组合
  var groups = that.layHeader.find(
    // 根据当前活动的表头 parentkey 属性查找其组合表头
    (th ? 'th[data-key=' + th.data('parentkey') + ']>' : '') + '.' + ELEM_GROUP,
  ); // 若无指向当前活动表头，则自下而上获取所有组合表头

  groups.css('width', 0);
  layui.each(groups.get().reverse(), function () {
    var othis = $(this);
    var key = othis.parent().data('key');
    var maxWidth = 0;

    that.layHeader
      .eq(0)
      .find('th[data-parentkey=' + key + ']')
      .width(function (i, width) {
        var oTh = $(this);
        if (oTh.hasClass(HIDE)) return;
        width > 0 && (maxWidth += width);
      });

    // 给组合表头赋值最大宽度
    if (maxWidth) othis.css('max-width', maxWidth - 1);

    // 若当前活动的组合表头仍存在上级，则继续向上设置
    if (th && othis.parent().data('parentkey')) {
      that.setGroupWidth(othis.parent());
    }
  });
  groups.css('width', 'auto');
};

// 动态分配列宽
Class.prototype.setColsWidth = function (opt) {
  var that = this;
  var options = that.config;
  var colNums = 0; // 列个数
  var autoColNums = 0; // 自动列宽的列个数
  var autoWidth = 0; // 自动列分配的宽度
  var countWidth = 0; // 所有列总宽度和
  var cntrWidth = that.setInit('width');
  var borderWidth = parseFloat(
    layui.getStyle(that.elem[0], 'border-left-width'),
  );
  var lastSpreadCol;
  var headerTableElem = that.layHeader.first().children('table');
  var mainTableElem = that.layMain.find('table');
  var isEmptyTable = that.layMain.find('tbody').is(':empty');
  var isInit = opt && opt.isInit;

  // 统计列个数和最后一个分配宽度的列
  that.eachCols(function (i, item) {
    if (!item.hide) {
      colNums++;
      if (!(item.width || item.type !== 'normal')) {
        lastSpreadCol = item;
      }
    }
  });

  // 减去边框差和滚动条宽
  cntrWidth =
    cntrWidth -
    (function () {
      return options.skin === 'line' || options.skin === 'nob'
        ? 2
        : colNums + 1;
    })() *
      borderWidth -
    that.getScrollWidth(that.layMain[0]);

  // 计算自动分配的宽度
  var getAutoWidth = function (back) {
    // 遍历所有列
    layui.each(options.cols, function (i1, item1) {
      layui.each(item1, function (i2, item2) {
        var width = 0;
        var minWidth = item2.minWidth || options.cellMinWidth; // 最小宽度
        var maxWidth = item2.maxWidth || options.cellMaxWidth; // 最大宽度

        if (!item2) {
          item1.splice(i2, 1);
          return;
        }

        if (item2.colGroup || item2.hide) return;

        if (!back) {
          width = item2.width || 0;
          if (/\d+%$/.test(width)) {
            // 列宽为百分比
            width = (parseFloat(width) / 100) * cntrWidth;
            width < minWidth && (width = minWidth);
            width > maxWidth && (width = maxWidth);
          } else if (!width) {
            // 列宽未填写
            item2.width = width = 0;
            autoColNums++;
          } else if (item2.type === 'normal') {
            // 若 width 小于 minWidth， 则将 width 值自动设为 minWidth 的值
            width < minWidth && (item2.width = width = minWidth);
            // 若 width 大于 maxWidth， 则将 width 值自动设为 maxWidth 的值
            width > maxWidth && (item2.width = width = maxWidth);
          }
        } else if (autoWidth && autoWidth < minWidth) {
          autoColNums--;
          width = minWidth;
        } else if (autoWidth && autoWidth > maxWidth) {
          autoColNums--;
          width = maxWidth;
        }

        if (item2.hide) width = 0;
        countWidth = countWidth + width;
      });
    });

    // 如果未填充满，则将剩余宽度平分
    cntrWidth > countWidth &&
      autoColNums > 0 &&
      (autoWidth = (cntrWidth - countWidth) / autoColNums);
  };

  getAutoWidth();
  getAutoWidth(true); // 重新检测分配的宽度是否低于最小列宽

  // 记录自动列数
  that.autoColNums = autoColNums = autoColNums > 0 ? autoColNums : 0;

  var pixelsForLastCol = cntrWidth;
  that.eachCols(function (i3, item3) {
    var minWidth = item3.minWidth || options.cellMinWidth;
    var maxWidth = item3.maxWidth || options.cellMaxWidth;

    if (
      item3.colGroup ||
      item3.hide ||
      (lastSpreadCol && lastSpreadCol.key === item3.key)
    )
      return;

    // 给未分配宽的列平均分配宽
    if (item3.width === 0) {
      that.cssRules(item3.key, function (item) {
        var newWidth = Math.round(
          (function () {
            if (autoWidth < minWidth) return minWidth;
            if (autoWidth > maxWidth) return maxWidth;
            return autoWidth;
          })(),
        );
        item.style.width = newWidth + 'px';
        pixelsForLastCol = pixelsForLastCol - newWidth;
      });
    }

    // 给设定百分比的列分配列宽
    else if (/\d+%$/.test(item3.width)) {
      that.cssRules(item3.key, function (item) {
        var width = Math.round((parseFloat(item3.width) / 100) * cntrWidth);
        width < minWidth && (width = minWidth);
        width > maxWidth && (width = maxWidth);
        item.style.width = width + 'px';
        pixelsForLastCol = pixelsForLastCol - width;
      });
    }

    // 给拥有普通 width 值的列分配最新列宽
    else {
      that.cssRules(item3.key, function (item) {
        item.style.width = item3.width + 'px';
        pixelsForLastCol = pixelsForLastCol - item3.width;
      });
    }
  });
  // 最后一列获取剩余的空间，避免舍入导致的布局问题
  if (lastSpreadCol) {
    that.cssRules(lastSpreadCol.key, function (item) {
      var minWidth = lastSpreadCol.minWidth || options.cellMinWidth;
      var maxWidth = lastSpreadCol.maxWidth || options.cellMaxWidth;
      var newWidth = Math.max(Math.min(pixelsForLastCol, maxWidth), minWidth);
      item.style.width = newWidth + 'px';

      if (!isInit && isEmptyTable) {
        // 将表格宽度设置为跟表头一样的宽度，使之可以出现底部滚动条，以便滚动查看所有字段
        mainTableElem.width(that.getContentWidth(headerTableElem));
      }
      // 二次校验，如果仍然出现横向滚动条（通常是 1px 的误差导致）
      // 不同屏幕分辨率、缩放水平以及浏览器渲染差异，可能会触发这个问题
      if (
        that.layMain.prop('offsetHeight') > that.layMain.prop('clientHeight')
      ) {
        item.style.width = parseFloat(item.style.width) - borderWidth + 'px';
      }
    });
  }

  if (!isInit && isEmptyTable) {
    // 将表格宽度设置为跟表头一样的宽度，使之可以出现底部滚动条，以便滚动查看所有字段
    mainTableElem.width(that.getContentWidth(headerTableElem));
  } else {
    mainTableElem.width('auto');
  }

  that.setGroupWidth();
};

// 重置表格尺寸/结构
Class.prototype.resize = function () {
  var that = this;

  var tableElemIsConnected =
    that.layMain &&
    ('isConnected' in that.layMain[0]
      ? that.layMain[0].isConnected
      : $.contains(document.body, that.layMain[0]));

  if (!tableElemIsConnected) return;

  that.fullSize(); // 让表格铺满
  that.setColsWidth(); // 自适应列宽
  that.scrollPatch(); // 滚动条补丁
};

// 表格重载
Class.prototype.reload = function (options, deep, type) {
  var that = this;

  options = options || {};
  delete that.haveInit;

  // 防止数组深度合并
  layui.each(options, function (key, item) {
    if (layui.type(item) === 'array') delete that.config[key];
  });

  // 对参数进行深度或浅扩展
  that.config = $.extend(deep, {}, that.config, options);
  if (type !== 'reloadData') {
    layui.each(that.config.cols, function (i1, item1) {
      layui.each(item1, function (i2, item2) {
        delete item2.colspan2;
      });
    });
    delete that.config.HAS_SET_COLS_PATCH;
  }
  // 执行渲染
  that.render(type);
};

// 异常提示
Class.prototype.errorView = function (html) {
  var that = this,
    elemNone = that.layMain.find('.' + NONE),
    layNone = $('<div class="' + NONE + '">' + (html || 'Error') + '</div>');

  if (elemNone[0]) {
    that.layNone.remove();
    elemNone.remove();
  }

  that.layFixed.addClass(HIDE);
  that.layMain.find('tbody').html('');

  that.layMain.append((that.layNone = layNone));

  // 异常情况下对 page 和 total 的内容处理
  that.layTotal.addClass(HIDE_V);
  that.layPage.find(ELEM_PAGE_VIEW).addClass(HIDE_V);

  table.cache[that.key] = []; //格式化缓存数据

  that.syncCheckAll();
  that.renderForm();
  that.setColsWidth();
  that.loading(false);
};

// 初始页码
Class.prototype.page = 1;

// 获得数据
Class.prototype.pullData = function (curr, opts) {
  var that = this;
  var options = that.config;
  // 同步表头父列的相关值
  options.HAS_SET_COLS_PATCH || that.setColsPatch();
  options.HAS_SET_COLS_PATCH = true;
  var request = options.request;
  var response = options.response;
  var res;
  var sort = function () {
    if (typeof options.initSort === 'object') {
      that.sort({
        field: options.initSort.field,
        type: options.initSort.type,
        reloadType: opts.type,
      });
    }
  };
  var done = function (res, origin) {
    that.setColsWidth();
    that.loading(false);
    typeof options.done === 'function' &&
      options.done(res, curr, res[response.countName], origin);
    typeof opts.done === 'function' && opts.done();
  };

  opts = opts || {};

  // 数据拉取前的回调
  typeof options.before === 'function' && options.before(options);
  that.startTime = new Date().getTime(); // 渲染开始时间

  if (opts.renderData) {
    // 将 cache 信息重新渲染
    res = {};
    res[response.dataName] = table.cache[that.key];
    res[response.countName] = options.url
      ? layui.type(options.page) === 'object'
        ? options.page.count
        : res[response.dataName].length
      : options.data.length;

    // 记录合计行数据
    if (typeof options.totalRow === 'object') {
      res[response.totalRowName] = $.extend({}, that.totalRow);
    }

    (that.renderData({
      res: res,
      curr: curr,
      count: res[response.countName],
      type: opts.type,
      sort: true,
    }),
      done(res, 'renderData'));
  } else if (options.url) {
    // Ajax请求
    var params = {};
    // 当 page 开启，默认自动传递 page、limit 参数
    if (options.page) {
      params[request.pageName] = curr;
      params[request.limitName] = options.limit;
    }

    // 参数
    var data = $.extend(params, options.where);
    if (
      options.contentType &&
      options.contentType.indexOf('application/json') == 0
    ) {
      // 提交 json 格式
      data = JSON.stringify(data);
    }

    that.loading(true);

    var ajaxOptions = {
      type: options.method || 'get',
      url: options.url,
      contentType: options.contentType,
      data: data,
      dataType: options.dataType || 'json',
      jsonpCallback: options.jsonpCallback,
      headers: options.headers || {},
      complete:
        typeof options.complete === 'function' ? options.complete : undefined,
      success: function (res) {
        // 若有数据解析的回调，则获得其返回的数据
        if (typeof options.parseData === 'function') {
          res = options.parseData(res) || res;
        }
        // 检查数据格式是否符合规范
        if (res[response.statusName] != response.statusCode) {
          that.errorView(
            res[response.msgName] ||
              i18n.$t('table.dataFormatError', {
                statusName: response.statusName,
                statusCode: response.statusCode,
              }),
          );
        } else {
          // 当前页不能超过总页数
          var count = res[response.countName];
          var pages = Math.ceil(count / options.limit) || 1;
          if (curr > pages) {
            curr = pages;
          }
          that.totalRow = res[response.totalRowName];
          (that.renderData({
            res: res,
            curr: curr,
            count: count,
            type: opts.type,
          }),
            sort());

          // 耗时（接口请求+视图渲染）
          options.time = new Date().getTime() - that.startTime + ' ms';
        }
        done(res, opts.type);
      },
      error: function (e, msg) {
        if (e && e.status === 0 && that._xhrAbort) {
          that._xhrAbort = false;
          return;
        }
        that.errorView(i18n.$t('table.xhrError', { msg: msg }));
        typeof options.error === 'function' && options.error(e, msg);
      },
    };

    if (options.ajax) {
      options.ajax(ajaxOptions, 'table');
    } else {
      // 4：代表响应已完成
      if (that._xhr && that._xhr.readyState !== 4) {
        that._xhrAbort = true;
        that._xhr.abort();
      }
      that._xhr = $.ajax(ajaxOptions);
    }
  } else if (layui.type(options.data) === 'array') {
    //已知数据
    res = {};
    var startLimit = curr * options.limit - options.limit;
    var newData = options.data.concat();

    res[response.dataName] = options.page
      ? newData.splice(startLimit, options.limit)
      : newData;
    res[response.countName] = options.data.length;

    // 记录合计行数据
    if (typeof options.totalRow === 'object') {
      res[response.totalRowName] = $.extend({}, options.totalRow);
    }
    that.totalRow = res[response.totalRowName];

    (that.renderData({
      res: res,
      curr: curr,
      count: res[response.countName],
      type: opts.type,
    }),
      sort());

    done(res, opts.type);
  }
};

// 遍历表头
Class.prototype.eachCols = function (callback) {
  var that = this;
  table.eachCols(null, callback, that.config.cols);
  return that;
};

// 获取表头参数项
Class.prototype.col = function (key) {
  try {
    key = key.split('-');
    return this.config.cols[key[1]][key[2]] || {};
  } catch (e) {
    hint.error(e);
    return {};
  }
};

Class.prototype.getTrHtml = function (data, sort, curr, trsObj) {
  var that = this;
  var options = that.config;
  var trs = (trsObj && trsObj.trs) || [];
  var trs_fixed = (trsObj && trsObj.trs_fixed) || [];
  var trs_fixed_r = (trsObj && trsObj.trs_fixed_r) || [];
  curr = curr || 1;

  layui.each(data, function (i1, item1) {
    var tds = [];
    var tds_fixed = [];
    var tds_fixed_r = [];
    var numbers = i1 + options.limit * (curr - 1) + 1; // 序号

    // 数组值是否为 object，如果不是，则自动转为 object
    if (typeof item1 !== 'object') {
      data[i1] = item1 = { LAY_KEY: item1 };
      try {
        table.cache[that.key][i1] = item1;
      } catch {
        // ignore
      }
    }

    //若数据项为空数组，则不往下执行（因为删除数据时，会将原有数据设置为 []）
    if (layui.type(item1) === 'array' && item1.length === 0) return;

    // 加入序号保留字段
    item1[table.config.numbersName] = numbers;

    // 记录下标，
    item1[table.config.indexName] = i1;
    if (!sort) item1[table.config.initIndexName] = i1; // 记录初始状态下标，仅用于内部恢复当前页表格排序

    // 遍历表头
    that.eachCols(function (i3, item3) {
      var field = item3.field || i3;
      var key = item3.key;
      var content = item1[field];

      if (content === undefined || content === null) content = '';
      if (item3.colGroup) return;

      // td 内容
      var td = [
        '<td data-field="' +
          field +
          '" data-key="' +
          key +
          '" ' +
          (function () {
            // 追加各种属性
            var attr = [];
            // 是否开启编辑。若 edit 传入函数，则根据函数的返回结果判断是否开启编辑
            (function (edit) {
              if (edit) attr.push('data-edit="' + edit + '"'); // 添加单元格编辑属性标识
            })(
              typeof item3.edit === 'function' ? item3.edit(item1) : item3.edit,
            );
            if (item3.templet)
              attr.push('data-content="' + util.escape(content) + '"'); // 自定义模板
            if (item3.toolbar) attr.push('data-off="true"'); // 行工具列关闭单元格事件
            if (item3.event) attr.push('lay-event="' + item3.event + '"'); //自定义事件
            if (item3.minWidth)
              attr.push('data-minwidth="' + item3.minWidth + '"'); // 单元格最小宽度
            if (item3.maxWidth)
              attr.push('data-maxwidth="' + item3.maxWidth + '"'); // 单元格最大宽度
            if (item3.style) attr.push('style="' + item3.style + '"'); // 自定义单元格样式
            return attr.join(' ');
          })() +
          ' class="' +
          (function () {
            // 追加样式
            var classNames = [];
            if (item3.hide) classNames.push(HIDE); // 插入隐藏列样式
            if (!item3.field) classNames.push(ELEM_COL_SPECIAL); // 插入特殊列样式
            return classNames.join(' ');
          })() +
          '">',
        '<div class="layui-table-cell laytable-cell-' +
          (function () {
            // 返回对应的CSS类标识
            return item3.type === 'normal'
              ? key
              : key + ' laytable-cell-' + item3.type;
          })() +
          '"' +
          (item3.align ? ' align="' + item3.align + '"' : '') +
          '>' +
          (function () {
            var tplData = $.extend(
              true,
              {
                LAY_COL: item3,
              },
              item1,
            );
            var checkName = table.config.checkName;
            var disabledName = table.config.disabledName;

            // 渲染不同风格的列
            switch (item3.type) {
              case 'checkbox': // 复选
                return (
                  '<input type="checkbox" name="layTableCheckbox" lay-skin="primary" ' +
                  (function () {
                    // 其他属性
                    var arr = [];

                    //如果是全选
                    if (item3[checkName]) {
                      item1[checkName] = item3[checkName];
                      if (item3[checkName]) arr[0] = 'checked';
                    }
                    if (tplData[checkName]) arr[0] = 'checked';

                    // 禁选
                    if (tplData[disabledName]) arr.push('disabled');

                    return arr.join(' ');
                  })() +
                  ' lay-type="layTableCheckbox">'
                );
              //break;
              case 'radio': // 单选
                return (
                  '<input type="radio" name="layTableRadio_' +
                  options.index +
                  '" ' +
                  (function () {
                    var arr = [];
                    if (tplData[checkName]) arr[0] = 'checked';
                    if (tplData[disabledName]) arr.push('disabled');
                    return arr.join(' ');
                  })() +
                  ' lay-type="layTableRadio">'
                );
              //break;
              case 'numbers':
                return numbers;
              //break;
            }

            //解析工具列模板
            if (item3.toolbar) {
              return laytpl($(item3.toolbar).html() || '').render(tplData);
            }
            return parseTempData.call(that, {
              item3: item3,
              content: content,
              tplData: tplData,
            });
          })(),
        '</div></td>',
      ].join('');

      tds.push(td);
      if (item3.fixed && item3.fixed !== 'right') tds_fixed.push(td);
      if (item3.fixed === 'right') tds_fixed_r.push(td);
    });

    // 添加 tr 属性
    var trAttr = (function () {
      var arr = ['data-index="' + i1 + '"'];
      if (item1[table.config.checkName])
        arr.push('class="' + ELEM_CHECKED + '"');
      return arr.join(' ');
    })();

    trs.push('<tr ' + trAttr + '>' + tds.join('') + '</tr>');
    trs_fixed.push('<tr ' + trAttr + '>' + tds_fixed.join('') + '</tr>');
    trs_fixed_r.push('<tr ' + trAttr + '>' + tds_fixed_r.join('') + '</tr>');
  });

  return {
    trs: trs,
    trs_fixed: trs_fixed,
    trs_fixed_r: trs_fixed_r,
  };
};

// 返回行节点代码
table.getTrHtml = function (id, data) {
  var that = getThisTable(id);
  return that.getTrHtml(data, null, that.page);
};

// 数据渲染
Class.prototype.renderData = function (opts) {
  var that = this;
  var options = that.config;

  var res = opts.res;
  var curr = opts.curr;
  var count = (that.count = opts.count);
  var sort = opts.sort;

  var data = res[options.response.dataName] || []; //列表数据
  var totalRowData = res[options.response.totalRowName]; //合计行数据
  var trs = [];
  var trs_fixed = [];
  var trs_fixed_r = [];

  // 渲染视图
  var render = function () {
    // 后续性能提升的重点
    if (!sort && that.sortKey) {
      return that.sort({
        field: that.sortKey.field,
        type: that.sortKey.sort,
        pull: true,
        reloadType: opts.type,
      });
    }

    that.getTrHtml(data, sort, curr, {
      trs: trs,
      trs_fixed: trs_fixed,
      trs_fixed_r: trs_fixed_r,
    });

    // 容器的滚动条位置
    if (!(options.scrollPos === 'fixed' && opts.type === 'reloadData')) {
      that.layBody.scrollTop(0);
    }
    if (options.scrollPos === 'reset') {
      that.layBody.scrollLeft(0);
    }

    that.layMain.find('.' + NONE).remove();
    that.layMain.find('tbody').html(trs.join(''));
    that.layFixLeft.find('tbody').html(trs_fixed.join(''));
    that.layFixRight.find('tbody').html(trs_fixed_r.join(''));

    // 渲染表单
    that.syncCheckAll();
    that.renderForm();

    // 因为 page 参数有可能发生变化 先重新铺满
    that.fullSize();

    // 滚动条补丁
    that.haveInit
      ? that.scrollPatch()
      : setTimeout(function () {
          that.scrollPatch();
        }, 50);
    that.haveInit = true;

    // reloadData 或 renderData 时，tbody 高度可能不变，需要主动同步
    if (that.needSyncFixedRowHeight) {
      that.calcFixedRowHeight();
    }

    layer.close(that.tipsIndex);
  };

  table.cache[that.key] = data; //记录数据

  //显示隐藏合计栏
  that.layTotal[data.length == 0 ? 'addClass' : 'removeClass'](HIDE_V);

  //显示隐藏分页栏
  that.layPage[options.page || options.pagebar ? 'removeClass' : 'addClass'](
    HIDE,
  );
  that.layPage
    .find(ELEM_PAGE_VIEW)
    [
      !options.page || count == 0 || (data.length === 0 && curr == 1)
        ? 'addClass'
        : 'removeClass'
    ](HIDE_V);

  //如果无数据
  if (data.length === 0) {
    return that.errorView(options.text.none);
  } else {
    that.layFixLeft.removeClass(HIDE);
  }

  //如果执行初始排序
  if (sort) {
    return render();
  }

  //正常初始化数据渲染
  render(); //渲染数据
  that.renderTotal(data, totalRowData); //数据合计
  that.layTotal && that.layTotal.removeClass(HIDE);

  //同步分页状态
  if (options.page) {
    options.page = $.extend(
      {
        elem: 'layui-table-page' + options.index,
        count: count,
        limit: options.limit,
        limits: options.limits || [10, 20, 30, 40, 50, 60, 70, 80, 90],
        groups: 3,
        layout: ['prev', 'page', 'next', 'skip', 'count', 'limit'],
        prev: '<i class="layui-icon">&#xe603;</i>',
        next: '<i class="layui-icon">&#xe602;</i>',
        jump: function (obj, first) {
          if (!first) {
            //分页本身并非需要做以下更新，下面参数的同步，主要是因为其它处理统一用到了它们
            //而并非用的是 options.page 中的参数（以确保分页未开启的情况仍能正常使用）
            that.page = obj.curr; //更新页码
            options.limit = obj.limit; //更新每页条数

            that.pullData(obj.curr);
          }
        },
      },
      options.page,
    );
    options.page.count = count; //更新总条数
    laypage.render(options.page);
  }
};

// 重新渲染数据
table.renderData = function (id) {
  var that = getThisTable(id);
  if (!that) {
    return;
  }

  that.pullData(that.page, {
    renderData: true,
    type: 'reloadData',
  });
};

// 数据合计行
Class.prototype.renderTotal = function (data, totalRowData) {
  var that = this;
  var options = that.config;
  var totalNums = {};

  if (!options.totalRow) return;

  layui.each(data, function (i1, item1) {
    // 若数据项为空数组，则不往下执行（因为删除数据时，会将原有数据设置为 []）
    if (layui.type(item1) === 'array' && item1.length === 0) return;

    that.eachCols(function (i3, item3) {
      var field = item3.field || i3,
        content = item1[field];

      if (item3.totalRow) {
        totalNums[field] = (totalNums[field] || 0) + (parseFloat(content) || 0);
      }
    });
  });

  that.dataTotal = []; // 记录合计行结果

  var tds = [];
  that.eachCols(function (i3, item3) {
    var field = item3.field || i3;

    // 合计数据的特定字段
    var TOTAL_NUMS = totalRowData && totalRowData[item3.field];

    // 合计数据的小数点位数处理
    var decimals = 'totalRowDecimals' in item3 ? item3.totalRowDecimals : 2;
    var thisTotalNum = totalNums[field]
      ? parseFloat(totalNums[field] || 0).toFixed(decimals)
      : '';

    // 合计内容
    var content = (function () {
      var text = item3.totalRowText || '';
      var tplData = {
        LAY_COL: item3,
      };

      tplData[field] = thisTotalNum;

      // 获取自动计算的合并内容
      var getContent = item3.totalRow
        ? parseTempData.call(that, {
            item3: item3,
            content: thisTotalNum,
            tplData: tplData,
          }) || text
        : text;

      // 如果直接传入了合计行数据，则不输出自动计算的结果
      return TOTAL_NUMS || getContent;
    })();

    // td 显示内容
    var tdContent = (function () {
      var totalRow = item3.totalRow || options.totalRow;

      // 如果 totalRow 参数为字符类型，则解析为自定义模版
      if (typeof totalRow === 'string') {
        return laytpl(totalRow).render(
          $.extend(
            {
              TOTAL_NUMS: TOTAL_NUMS || totalNums[field],
              TOTAL_ROW: totalRowData || {},
              LAY_COL: item3,
            },
            item3,
          ),
        );
      }

      return content;
    })();

    // 合计原始结果
    item3.field &&
      that.dataTotal.push({
        field: item3.field,
        total: $('<div>' + tdContent + '</div>').text(),
      });

    // td 容器
    var td = [
      '<td data-field="' +
        field +
        '" data-key="' +
        item3.key +
        '" ' +
        (function () {
          var attr = [];
          if (item3.minWidth)
            attr.push('data-minwidth="' + item3.minWidth + '"'); // 单元格最小宽度
          if (item3.maxWidth)
            attr.push('data-maxwidth="' + item3.maxWidth + '"'); // 单元格最小宽度
          if (item3.style) attr.push('style="' + item3.style + '"'); // 自定义单元格样式
          return attr.join(' ');
        })() +
        ' class="' +
        (function () {
          // 追加样式
          var classNames = [];
          if (item3.hide) classNames.push(HIDE); // 插入隐藏列样式
          if (!item3.field) classNames.push(ELEM_COL_SPECIAL); // 插入特殊列样式
          return classNames.join(' ');
        })() +
        '">',
      '<div class="layui-table-cell laytable-cell-' +
        (function () {
          // 返回对应的CSS类标识
          var key = item3.key;
          return item3.type === 'normal'
            ? key
            : key + ' laytable-cell-' + item3.type;
        })() +
        '"' +
        (function () {
          var attr = [];
          if (item3.align) attr.push('align="' + item3.align + '"'); // 对齐方式
          return attr.join(' ');
        })() +
        '>' +
        tdContent,
      '</div></td>',
    ].join('');

    tds.push(td);
  });

  var patchElem = that.layTotal.find('.layui-table-patch'); // 可能存在滚动条补丁
  that.layTotal
    .find('tbody')
    .html(
      '<tr>' +
        tds.join('') +
        (patchElem.length ? patchElem.get(0).outerHTML : '') +
        '</tr>',
    );
};

//找到对应的列元素
Class.prototype.getColElem = function (parent, key) {
  // var that = this;
  //var options = that.config;
  return parent.eq(0).find('.laytable-cell-' + key + ':eq(0)');
};

// 渲染表单
Class.prototype.renderForm = function (type) {
  var that = this;
  // var options = that.config;
  var filter = that.elem.attr('lay-filter');
  form.render(type, filter);
};

// 定向渲染表单
Class.prototype.renderFormByElem = function (elem) {
  layui.each(['input', 'select'], function (i, formType) {
    form.render(elem.find(formType));
  });
};

// 同步全选按钮状态
Class.prototype.syncCheckAll = function () {
  var that = this;
  var options = that.config;
  var checkAllElem = that.layHeader.find('input[name="layTableCheckbox"]');
  var syncColsCheck = function (checked) {
    that.eachCols(function (i, item) {
      if (item.type === 'checkbox') {
        item[options.checkName] = checked;
      }
    });
    return checked;
  };
  var checkStatus = table.checkStatus(that.key);

  if (!checkAllElem[0]) return;

  // 选中状态
  syncColsCheck(checkStatus.isAll);
  checkAllElem.prop({
    checked: checkStatus.isAll,
    indeterminate: !checkStatus.isAll && checkStatus.data.length, // 半选
  });
};

// 标记当前活动行背景色
Class.prototype.setRowActive = function (index, className, removeClass) {
  var that = this;
  // var options = that.config;
  var tr = that.layBody.find('tr[data-index="' + index + '"]');
  className = className || 'layui-table-click';

  if (removeClass) return tr.removeClass(className);

  tr.addClass(className);
  tr.siblings('tr').removeClass(className);
};

// 设置行选中状态
Class.prototype.setRowChecked = function (opts) {
  var that = this;
  var options = that.config;
  var isCheckAll = opts.index === 'all'; // 是否操作全部
  var isCheckMult = layui.type(opts.index) === 'array'; // 是否操作多个
  var isCheckAllOrMult = isCheckAll || isCheckMult; // 是否全选或多选

  // treeTable 内部已处理选中，此处不再处理
  if (options.tree && options.tree.view) return;

  // 全选或多选时
  if (isCheckAllOrMult) {
    that.layBox.addClass(DISABLED_TRANSITION); // 减少回流
    if (opts.type === 'radio') return; // radio 不允许全选或多选
  }

  if (isCheckMult) {
    var makeMap = {};
    layui.each(opts.index, function (i, v) {
      makeMap[v] = true;
    });
    opts.index = makeMap;
  }

  // 匹配行元素
  var tbody = that.layBody.children('.layui-table').children('tbody');
  var selector = isCheckAllOrMult
    ? 'tr'
    : 'tr[data-index="' + opts.index + '"]';
  var tr = (function (tr) {
    return isCheckAll
      ? tr
      : tr.filter(
          isCheckMult
            ? function () {
                var dataIndex = $(this).data('index');
                return opts.index[dataIndex];
              }
            : '[data-index="' + opts.index + '"]',
        );
  })(tbody.children(selector));

  // 默认属性
  opts = $.extend(
    {
      type: 'checkbox', // 选中方式
    },
    opts,
  );

  // 同步数据选中属性值
  var thisData = table.cache[that.key];
  var existChecked = 'checked' in opts;

  // 若为单选框，则单向选中；若为复选框，则切换选中。
  var getChecked = function (value) {
    return opts.type === 'radio' ? true : existChecked ? opts.checked : !value;
  };

  var radioCheckedIndex;

  // 给匹配行设置选中状态
  tr.each(function () {
    var el = $(this);
    var i = el.attr('data-index');
    var item = thisData[i];

    if (!i) return; // 此时 el 通常为静态表格嵌套时的原始模板

    // 绕过空项和禁用项
    if (layui.type(item) === 'array' || item[options.disabledName]) {
      return;
    }

    // 标记数据选中状态
    var checked = (item[options.checkName] = getChecked(
      el.hasClass(ELEM_CHECKED),
    ));

    // 标记当前行背景色
    el.toggleClass(ELEM_CHECKED, !!checked);

    // 若为 radio 类型，则取消其他行选中背景色
    if (opts.type === 'radio') {
      radioCheckedIndex = i;
      el.siblings().removeClass(ELEM_CHECKED);
    }
  });

  // 若为 radio 类型，移除其他行数据选中状态
  if (radioCheckedIndex) {
    layui.each(thisData, function (i, item) {
      if (Number(radioCheckedIndex) !== Number(i)) {
        delete item[options.checkName];
      }
    });
  }

  // 若存在复选框或单选框，则标注选中状态样式
  var td = tr.children('td').children('.layui-table-cell');
  var checkedElem = td.children(
    'input[lay-type="' +
      ({
        radio: 'layTableRadio',
        checkbox: 'layTableCheckbox',
      }[opts.type] || 'checkbox') +
      '"]:not(:disabled)',
  );
  var checkedSameElem = checkedElem.last();
  var fixRElem = checkedSameElem.closest(ELEM_FIXR);

  (opts.type === 'radio' && fixRElem.hasClass(HIDE)
    ? checkedElem.first()
    : checkedElem
  ).prop('checked', getChecked(checkedSameElem.prop('checked')));

  that.syncCheckAll();

  if (isCheckAllOrMult) {
    setTimeout(function () {
      that.layBox.removeClass(DISABLED_TRANSITION);
    }, 100);
  }
};

// 数据排序
Class.prototype.sort = function (opts) {
  // field, type, pull, fromEvent
  var that = this;
  var field;
  var res = {};
  var options = that.config;
  var filter = options.elem.attr('lay-filter');
  var data = table.cache[that.key],
    thisData;

  opts = opts || {};

  // 字段匹配
  if (typeof opts.field === 'string') {
    field = opts.field;
    that.layHeader.find('th').each(function () {
      var othis = $(this);
      var _field = othis.data('field');
      if (_field === opts.field) {
        opts.field = othis;
        field = _field;
        return false;
      }
    });
  }

  try {
    field = field || opts.field.data('field');
    var key = opts.field.data('key');

    // 如果欲执行的排序已在状态中，则不执行渲染
    if (that.sortKey && !opts.pull) {
      if (field === that.sortKey.field && opts.type === that.sortKey.sort) {
        return;
      }
    }

    var elemSort = that.layHeader
      .find('th .laytable-cell-' + key)
      .find(ELEM_SORT);
    that.layHeader.find('th').find(ELEM_SORT).removeAttr('lay-sort'); // 清除其它标题排序状态
    elemSort.attr('lay-sort', opts.type || null);
    that.layFixed.find('th');
  } catch {
    hint.error("Table modules: sort field '" + field + "' not matched");
  }

  // 记录排序索引和类型
  that.sortKey = {
    field: field,
    sort: opts.type,
  };

  // 默认为前端自动排序。如果否，则需自主排序（通常为服务端处理好排序）
  if (options.autoSort) {
    if (opts.type === 'asc') {
      //升序
      thisData = layui.sort(data, field, null, true);
    } else if (opts.type === 'desc') {
      //降序
      thisData = layui.sort(data, field, true, true);
    } else {
      // 清除排序
      thisData = layui.sort(data, table.config.initIndexName, null, true);
      delete that.sortKey;
      delete options.initSort;
    }
  }

  res[options.response.dataName] = thisData || data;

  // 重载数据
  that.renderData({
    res: res,
    curr: that.page,
    count: that.count,
    sort: true,
    type: opts.reloadType,
  });

  // 排序是否来自于点击表头事件触发
  if (opts.fromEvent) {
    options.initSort = {
      field: field,
      type: opts.type,
    };
    layui.event.call(
      opts.field,
      MOD_NAME,
      'sort(' + filter + ')',
      $.extend(
        {
          config: options,
        },
        options.initSort,
      ),
    );
  }
};

// 请求 loading
Class.prototype.loading = function (show) {
  var that = this;
  var options = that.config;

  if (options.loading) {
    that.layBox.find(ELEM_INIT).toggleClass(HIDE, !show);
  }
};

// 获取对应单元格的 cssRules
Class.prototype.cssRules = function (key, callback) {
  var that = this;
  var style = that.elem.children('style')[0];

  lay.getStyleRules(style, function (item) {
    if (item.selectorText === '.laytable-cell-' + key) {
      callback(item);
      return true;
    }
  });
};

// 让表格铺满
Class.prototype.fullSize = function () {
  var that = this;
  var options = that.config;
  var height = options.height;
  var bodyHeight;
  var MIN_HEIGHT = 135;

  if (that.fullHeightGap) {
    height = _WIN.height() - that.fullHeightGap;
    if (height < MIN_HEIGHT) height = MIN_HEIGHT;
    // that.elem.css('height', height);
  } else if (that.parentDiv && that.parentHeightGap) {
    height = $(that.parentDiv).height() - that.parentHeightGap;
    if (height < MIN_HEIGHT) height = MIN_HEIGHT;
    // that.elem.css("height", height);
  } else if (that.customHeightFunc) {
    height = that.customHeightFunc();
    if (height < MIN_HEIGHT) height = MIN_HEIGHT;
  }

  // 如果多级表头，则填补表头高度
  if (options.cols.length > 1) {
    // 补全高度
    var th = that.layFixed.find(ELEM_HEADER).find('th');
    // 固定列表头同步跟本体 th 一致高度
    var headerMain = that.layHeader.first();
    layui.each(th, function (thIndex, thElem) {
      thElem = $(thElem);
      thElem.height(
        headerMain
          .find('th[data-key="' + thElem.attr('data-key') + '"]')
          .height() + 'px',
      );
    });
  }

  if (!height) return;

  // 减去列头区域的高度 --- 此处的数字常量是为了防止容器处在隐藏区域无法获得高度的问题，只对默认尺寸表格做支持
  bodyHeight = parseFloat(height) - (that.layHeader.outerHeight() || 39);

  // 减去工具栏的高度
  if (options.toolbar) {
    bodyHeight -= that.layTool.outerHeight() || 51;
  }

  // 减去统计栏的高度
  if (options.totalRow) {
    bodyHeight -= that.layTotal.outerHeight() || 40;
  }

  // 减去分页栏的高度
  if (options.page || options.pagebar) {
    bodyHeight -= that.layPage.outerHeight() || 43;
  }

  if (options.maxHeight) {
    layui.each(
      { elem: height, layMain: bodyHeight },
      function (elemName, elemHeight) {
        that[elemName].css({
          height: 'auto',
          maxHeight: elemHeight + 'px',
        });
      },
    );
  } else {
    that.layMain.outerHeight(bodyHeight);
  }
};

//获取滚动条宽度
Class.prototype.getScrollWidth = function (elem) {
  var width;
  if (elem) {
    width = elem.offsetWidth - elem.clientWidth;
  } else {
    elem = document.createElement('div');
    elem.style.width = '100px';
    elem.style.height = '100px';
    elem.style.overflowY = 'scroll';

    document.body.appendChild(elem);
    width = elem.offsetWidth - elem.clientWidth;
    document.body.removeChild(elem);
  }
  return width;
};

// 滚动条补丁
Class.prototype.scrollPatch = function () {
  var that = this;
  var layMainTable = that.layMain.children('table');
  var scrollWidth = that.layMain.width() - that.layMain.prop('clientWidth'); // 纵向滚动条宽度
  var scrollHeight = that.layMain.height() - that.layMain.prop('clientHeight'); // 横向滚动条高度
  // var getScrollWidth = that.getScrollWidth(that.layMain[0]); // 获取主容器滚动条宽度，如果有的话
  var outWidth = layMainTable.outerWidth() - that.layMain.width(); // 表格内容器的超出宽度

  // 添加补丁
  var addPatch = function (elem) {
    if (scrollWidth && scrollHeight) {
      elem = elem.eq(0);
      if (!elem.find('.layui-table-patch')[0]) {
        var patchElem = $(
          '<th class="layui-table-patch"><div class="layui-table-cell"></div></th>',
        ); // 补丁元素
        patchElem.find('div').css({
          width: scrollWidth,
        });
        elem.find('tr').append(patchElem);
      }
    } else {
      elem.find('.layui-table-patch').remove();
    }
  };

  addPatch(that.layHeader);
  addPatch(that.layTotal);

  // 固定列区域高度
  var mainHeight = that.layMain.height();
  var fixHeight = mainHeight - scrollHeight;

  that.layFixed
    .find(ELEM_BODY)
    .css('height', layMainTable.height() >= fixHeight ? fixHeight : 'auto')
    .scrollTop(that.layMain.scrollTop()); // 固定列滚动条高度

  // 表格宽度小于容器宽度时，隐藏固定列
  that.layFixRight[
    table.cache[that.key] && table.cache[that.key].length && outWidth > 0
      ? 'removeClass'
      : 'addClass'
  ](HIDE);

  // 操作栏
  that.layFixRight.css('right', scrollWidth);
};

/**
 * @typedef updateRowOptions
 * @prop {number} index - 行索引
 * @prop {Object.<string, any>} data - 行数据
 * @prop {boolean | ((field, index) => boolean)} [related] - 更新其他包含自定义模板且可能有所关联的列视图
 */
/**
 * 更新指定行
 * @param {updateRowOptions | updateRowOptions[]} opts
 * @param {(field: string, value: any) => void} [callback] - 更新每个字段时的回调函数
 */
Class.prototype.updateRow = function (opts, callback) {
  opts = layui.type(opts) === 'array' ? opts : [opts];

  var that = this;
  var ELEM_CELL = '.layui-table-cell';
  var dataCache = table.cache[that.key] || [];

  var update = function (opt) {
    var index = opt.index;
    var row = opt.data;
    var related = opt.related;

    var data = dataCache[index] || {};
    var tr = that.layBody.find('tr[data-index="' + index + '"]');

    // 更新缓存中的数据
    layui.each(row, function (key, value) {
      data[key] = value;
      callback && callback(key, value);
    });

    // 更新单元格
    that.eachCols(function (i, item3) {
      var field = String(item3.field || i);
      var shouldUpdate =
        field in row ||
        ((typeof related === 'function' ? related(field, i) : related) &&
          (item3.templet || item3.toolbar));
      if (shouldUpdate) {
        var td = tr.children('td[data-field="' + field + '"]');
        var cell = td.children(ELEM_CELL);
        var content = data[item3.field];
        cell.html(
          parseTempData.call(that, {
            item3: item3,
            content: content,
            tplData: $.extend(
              {
                LAY_COL: item3,
              },
              data,
            ),
          }),
        );
        td.data('content', content);
        that.renderFormByElem(cell);
      }
    });
  };

  layui.each(opts, function (i, opt) {
    update(opt);
  });
};

/**
 * 更新指定行
 * @param {string} id - table ID
 * @param {updateRowOptions | updateRowOptions[]} options
 */
table.updateRow = function (id, options) {
  var that = getThisTable(id);
  return that.updateRow(options);
};

// 事件处理
Class.prototype.events = function () {
  var that = this;
  var options = that.config;

  var filter = options.elem.attr('lay-filter');
  var th = that.layHeader.find('th');
  var ELEM_CELL = '.layui-table-cell';

  var _BODY = $('body');
  var dict = {};

  // 头部工具栏操作事件
  that.layTool.on('click', '*[lay-event]', function (e) {
    var othis = $(this);
    var events = othis.attr('lay-event');
    var data = table.cache[options.id];

    // 弹出工具下拉面板
    var openPanel = function (sets) {
      var list = $(sets.list);
      var panel = $('<ul class="' + ELEM_TOOL_PANEL + '"></ul>');

      panel.html(list);

      // 限制最大高度
      if (options.height) {
        panel.css(
          'max-height',
          options.height - (that.layTool.outerHeight() || 50),
        );
      }

      // 插入元素
      othis.find('.' + ELEM_TOOL_PANEL)[0] || othis.append(panel);
      that.renderForm();

      panel.on('click', function (e) {
        layui.stope(e);
      });

      sets.done && sets.done(panel, list);
    };

    layui.stope(e);
    _DOC.trigger('table.tool.panel.remove');
    layer.close(that.tipsIndex);

    // 头部工具栏右侧图标
    layui.each(options.defaultToolbar, function (index, item) {
      if (item.layEvent === events) {
        typeof item.onClick === 'function' &&
          item.onClick({
            data: data,
            config: options,
            openPanel: openPanel,
            elem: othis,
          });
        return true;
      }
    });

    // table toolbar 事件
    layui.event.call(
      this,
      MOD_NAME,
      'toolbar(' + filter + ')',
      $.extend(
        {
          event: events,
          config: options,
        },
        {},
      ),
    );
  });

  // 表头自定义元素事件
  that.layHeader.on('click', '*[lay-event]', function () {
    var othis = $(this);
    var events = othis.attr('lay-event');
    var th = othis.closest('th');
    var key = th.data('key');
    var col = that.col(key);

    layui.event.call(
      this,
      MOD_NAME,
      'colTool(' + filter + ')',
      $.extend(
        {
          event: events,
          config: options,
          col: col,
        },
        {},
      ),
    );
  });

  // 分页栏操作事件
  that.layPagebar.on('click', '*[lay-event]', function () {
    var othis = $(this);
    var events = othis.attr('lay-event');

    layui.event.call(
      this,
      MOD_NAME,
      'pagebar(' + filter + ')',
      $.extend(
        {
          event: events,
          config: options,
        },
        {},
      ),
    );
  });

  // 拖拽调整宽度
  th.on('mousemove', function (e) {
    var othis = $(this);
    var oLeft = othis.offset().left;
    var pLeft = e.clientX - oLeft;
    if (othis.data('unresize') || thisTable.eventMoveElem) {
      return;
    }
    dict.allowResize = othis.width() - pLeft <= 10; //是否处于拖拽允许区域
    _BODY.css('cursor', dict.allowResize ? 'col-resize' : '');
  })
    .on('mouseleave', function () {
      // var othis = $(this);
      if (thisTable.eventMoveElem) return;
      dict.allowResize = false;
      _BODY.css('cursor', '');
    })
    .on('mousedown', function (e) {
      var othis = $(this);
      if (dict.allowResize) {
        var key = othis.data('key');
        e.preventDefault();
        dict.offset = [e.clientX, e.clientY]; //记录初始坐标

        that.cssRules(key, function (item) {
          var width = item.style.width || othis.outerWidth();
          dict.rule = item;
          dict.ruleWidth = parseFloat(width);
          dict.minWidth = othis.data('minwidth') || options.cellMinWidth;
          dict.maxWidth = othis.data('maxwidth') || options.cellMaxWidth;
        });

        // 临时记录当前拖拽信息
        othis.data(DATA_MOVE_NAME, dict);
        thisTable.eventMoveElem = othis;
      }
    });

  // 拖拽中
  if (!thisTable.docEvent) {
    _DOC
      .on('mousemove', function (e) {
        if (thisTable.eventMoveElem) {
          var dict = thisTable.eventMoveElem.data(DATA_MOVE_NAME) || {};

          thisTable.eventMoveElem.data('resizing', 1);
          e.preventDefault();

          if (dict.rule) {
            var setWidth = dict.ruleWidth + e.clientX - dict.offset[0];
            var id = thisTable.eventMoveElem
              .closest('.' + ELEM_VIEW)
              .attr(MOD_ID);
            var thatTable = getThisTable(id);

            if (!thatTable) return;

            if (setWidth < dict.minWidth) setWidth = dict.minWidth;
            if (setWidth > dict.maxWidth) setWidth = dict.maxWidth;

            dict.rule.style.width = setWidth + 'px';
            thatTable.setGroupWidth(thisTable.eventMoveElem);
            layer.close(that.tipsIndex);
          }
        }
      })
      .on('mouseup', function () {
        if (thisTable.eventMoveElem) {
          var th = thisTable.eventMoveElem; // 当前触发拖拽的 th 元素
          var id = th.closest('.' + ELEM_VIEW).attr(MOD_ID);
          var thatTable = getThisTable(id);

          if (!thatTable) return;

          var key = th.data('key');
          var col = thatTable.col(key);
          var filter = thatTable.config.elem.attr('lay-filter');

          // 重置过度信息
          dict = {};
          _BODY.css('cursor', '');
          thatTable.scrollPatch();

          // 清除当前拖拽信息
          th.removeData(DATA_MOVE_NAME);
          delete thisTable.eventMoveElem;

          // 列拖拽宽度后的事件
          thatTable.cssRules(key, function (item) {
            col.width = parseFloat(item.style.width);
            layui.event.call(th[0], MOD_NAME, 'colResized(' + filter + ')', {
              col: col,
              config: thatTable.config,
            });
          });
        }
      });
  }

  // 已给 document 执行全局事件，避免重复绑定
  thisTable.docEvent = true;

  // 排序
  th.on('click', function () {
    var othis = $(this);
    var elemSort = othis.find(ELEM_SORT);
    var nowType = elemSort.attr('lay-sort');
    var type;

    // 排序不触发的条件
    if (!elemSort[0] || othis.data('resizing') === 1) {
      return othis.removeData('resizing');
    }

    if (nowType === 'asc') {
      type = 'desc';
    } else if (nowType === 'desc') {
      type = null;
    } else {
      type = 'asc';
    }
    that.sort({
      field: othis,
      type: type,
      fromEvent: true,
    });
  })
    .find(ELEM_SORT + ' .layui-edge ')
    .on('click', function (e) {
      var othis = $(this);
      var index = othis.index();
      var field = othis.parents('th').eq(0).data('field');
      layui.stope(e);
      if (index === 0) {
        that.sort({
          field: field,
          type: 'asc',
          fromEvent: true,
        });
      } else {
        that.sort({
          field: field,
          type: 'desc',
          fromEvent: true,
        });
      }
    });

  //数据行中的事件返回的公共对象成员
  var commonMember = (that.commonMember = function (sets) {
    var othis = $(this);
    var index = othis.parents('tr').eq(0).data('index');
    var tr = that.layBody.find('tr[data-index="' + index + '"]');
    var data = table.cache[that.key] || [];

    data = data[index] || {};

    // 事件返回的公共成员
    var obj = {
      tr: tr, // 行元素
      config: options,
      data: table.clearCacheKey(data), // 当前行数据
      dataCache: data, // 当前行缓存中的数据
      index: index,
      del: function () {
        // 删除行数据
        table.cache[that.key][index] = [];
        tr.remove();
        that.scrollPatch();
      },
      update: function (fields, related) {
        // 修改行数据
        fields = fields || {};
        that.updateRow(
          {
            index: index,
            data: fields,
            related: related,
          },
          function (key, value) {
            obj.data[key] = value;
          },
        );
      },
      // 设置行选中状态
      setRowChecked: function (opts) {
        that.setRowChecked(
          $.extend(
            {
              index: index,
            },
            opts,
          ),
        );
      },
      // 获取当前列
    };

    return $.extend(obj, sets);
  });

  // 复选框选择（替代元素的 click 事件）
  that.elem.on('click', 'input[name="layTableCheckbox"]+', function (e) {
    var othis = $(this);
    var td = othis.closest('td');
    var checkbox = othis.prev();
    // var children = that.layBody.find('input[name="layTableCheckbox"]');
    var index = checkbox.parents('tr').eq(0).data('index');
    var checked = checkbox[0].checked;
    var isAll = checkbox.attr('lay-filter') === 'layTableAllChoose';

    if (checkbox[0].disabled) return;

    // 全选
    if (isAll) {
      that.setRowChecked({
        index: 'all',
        checked: checked,
      });
    } else {
      that.setRowChecked({
        index: index,
        checked: checked,
      });
    }

    layui.stope(e);

    // 事件
    layui.event.call(
      checkbox[0],
      MOD_NAME,
      'checkbox(' + filter + ')',
      commonMember.call(checkbox[0], {
        checked: checked,
        type: isAll ? 'all' : 'one',
        getCol: function () {
          // 获取当前列的表头配置信息
          return that.col(td.data('key'));
        },
      }),
    );
  });

  // 单选框选择
  that.elem.on('click', 'input[lay-type="layTableRadio"]+', function (e) {
    var othis = $(this);
    var td = othis.closest('td');
    var radio = othis.prev();
    var checked = radio[0].checked;
    var index = radio.parents('tr').eq(0).data('index');

    layui.stope(e);
    if (radio[0].disabled) return false;

    // 标注选中样式
    that.setRowChecked({
      type: 'radio',
      index: index,
    });

    // 事件
    layui.event.call(
      radio[0],
      MOD_NAME,
      'radio(' + filter + ')',
      commonMember.call(radio[0], {
        checked: checked,
        getCol: function () {
          // 获取当前列的表头配置信息
          return that.col(td.data('key'));
        },
      }),
    );
  });

  // 行事件
  that.layBody
    .on('mouseenter', 'tr', function () {
      // 鼠标移入行
      var othis = $(this);
      var index = othis.index();
      if (othis.data('off')) return; // 不触发事件
      var trsElem = that.layBody.find('tr:eq(' + index + ')');
      trsElem.addClass(ELEM_HOVER);
      if (that.needSyncFixedRowHeight) {
        that.fixedRowHeightPatchOnHover(this, trsElem, true);
      }
    })
    .on('mouseleave', 'tr', function () {
      // 鼠标移出行
      var othis = $(this);
      var index = othis.index();
      if (othis.data('off')) return; // 不触发事件
      var trsElem = that.layBody.find('tr:eq(' + index + ')');
      trsElem.removeClass(ELEM_HOVER);
      if (that.needSyncFixedRowHeight) {
        that.fixedRowHeightPatchOnHover(this, trsElem, false);
      }
    })
    .on('click', 'tr', function (e) {
      // 单击行
      setRowEvent.call(this, 'row', e);
    })
    .on('dblclick', 'tr', function (e) {
      // 双击行
      setRowEvent.call(this, 'rowDouble', e);
    })
    .on('contextmenu', 'tr', function (e) {
      // 菜单
      if (!options.defaultContextmenu) e.preventDefault();
      setRowEvent.call(this, 'rowContextmenu', e);
    });

  // 创建行单击、双击、菜单事件
  var setRowEvent = function (eventType, e) {
    var othis = $(this);
    if (othis.data('off')) return; // 不触发事件

    // 不触发「行单/双击事件」的子元素
    if (eventType !== 'rowContextmenu') {
      var UNROW = [
        '.layui-form-checkbox',
        '.layui-form-switch',
        '.layui-form-radio',
        '[lay-unrow]',
        '[lay-type="layTableCheckbox"]',
        '[lay-type="layTableRadio"]',
      ].join(',');

      if ($(e.target).is(UNROW) || $(e.target).closest(UNROW)[0]) {
        return;
      }
    }

    layui.event.call(
      this,
      MOD_NAME,
      eventType + '(' + filter + ')',
      commonMember.call(othis.children('td')[0], {
        e: e,
      }),
    );
  };

  // 渲染单元格编辑状态
  var renderGridEdit = function (othis, e) {
    othis = $(othis);

    if (othis.data('off')) return; // 不触发事件

    var field = othis.data('field');
    var key = othis.data('key');
    var col = that.col(key);
    var index = othis.closest('tr').data('index');
    var data = table.cache[that.key][index];
    // var elemCell = othis.children(ELEM_CELL);

    // 是否开启编辑
    // 若 edit 传入函数，则根据函数的返回结果判断是否开启编辑
    var editType = typeof col.edit === 'function' ? col.edit(data) : col.edit;

    // 显示编辑表单
    if (editType) {
      var input = $(
        (function () {
          var inputElem =
            '<input class="layui-input ' + ELEM_EDIT + '" lay-unrow>';
          if (editType === 'textarea') {
            inputElem =
              '<textarea class="layui-input ' +
              ELEM_EDIT +
              '" lay-unrow></textarea>';
          }
          return inputElem;
        })(),
      );
      input[0].value = (function (val) {
        return val === undefined || val === null ? '' : val;
      })(othis.data('content') || data[field]);
      othis.find('.' + ELEM_EDIT)[0] || othis.append(input);
      input.focus();
      e && layui.stope(e);
    }
  };

  // 单元格编辑 - 输入框内容被改变的事件
  that.layBody
    .on('change', '.' + ELEM_EDIT, function () {
      var othis = $(this);
      var td = othis.parent();
      var value = this.value;
      var field = othis.parent().data('field');
      var index = othis.closest('tr').data('index');
      var data = table.cache[that.key][index];

      //事件回调的参数对象
      var params = commonMember.call(td[0], {
        value: value,
        field: field,
        oldValue: data[field], // 编辑前的值
        td: td,
        reedit: function () {
          // 重新编辑
          setTimeout(function () {
            // 重新渲染为编辑状态
            renderGridEdit(params.td);

            // 将字段缓存的值恢复到编辑之前的值
            var obj = {};
            obj[field] = params.oldValue;
            params.update(obj);
          });
        },
        getCol: function () {
          // 获取当前列的表头配置信息
          return that.col(td.data('key'));
        },
      });

      // 更新缓存中的值
      var obj = {}; //变更的键值
      obj[field] = value;
      params.update(obj);

      // 执行 API 编辑事件
      layui.event.call(td[0], MOD_NAME, 'edit(' + filter + ')', params);
    })
    .on('blur', '.' + ELEM_EDIT, function () {
      // 单元格编辑 - 恢复非编辑状态事件
      $(this).remove(); // 移除编辑状态
    });

  // 表格主体单元格触发编辑的事件
  that.layBody
    .on(options.editTrigger, 'td', function (e) {
      renderGridEdit(this, e);
    })
    .on('mouseenter', 'td', function () {
      showGridExpandIcon.call(this);
    })
    .on('mouseleave', 'td', function () {
      showGridExpandIcon.call(this, 'hide');
    });

  // 表格合计栏单元格 hover 显示展开图标
  that.layTotal
    .on('mouseenter', 'td', function () {
      showGridExpandIcon.call(this);
    })
    .on('mouseleave', 'td', function () {
      showGridExpandIcon.call(this, 'hide');
    });

  // 显示单元格展开图标
  // var ELEM_GRID = 'layui-table-grid';
  var ELEM_GRID_DOWN = 'layui-table-grid-down';
  // var ELEM_GRID_PANEL = 'layui-table-grid-panel';
  var showGridExpandIcon = function (hide) {
    var othis = $(this);
    var elemCell = othis.children(ELEM_CELL);

    if (othis.data('off')) return; // 不触发事件
    if (othis.parent().hasClass(ELEM_EXPAND)) return; // 是否已为展开状态

    if (hide) {
      othis.find('.layui-table-grid-down').remove();
    } else if (
      (elemCell.prop('scrollWidth') > elemCell.prop('clientWidth') ||
        elemCell.find('br').length > 0) &&
      !options.lineStyle
    ) {
      if (elemCell.find('.' + ELEM_GRID_DOWN)[0]) return;
      othis.append(
        '<div class="' +
          ELEM_GRID_DOWN +
          '"><i class="layui-icon layui-icon-down"></i></div>',
      );
    }
  };
  // 展开单元格内容
  var gridExpand = function (e, expandedMode) {
    var othis = $(this);
    var td = othis.parent();
    var key = td.data('key');
    var col = that.col(key);
    var index = td.parent().data('index');
    var elemCell = td.children(ELEM_CELL);
    var ELEM_CELL_C = 'layui-table-cell-c';
    var elemCellClose = $(
      '<i class="layui-icon layui-icon-up ' + ELEM_CELL_C + '">',
    );

    expandedMode = expandedMode || col.expandedMode || options.cellExpandedMode;

    // 展开风格
    if (expandedMode === 'tips') {
      // TIPS 展开风格
      that.tipsIndex = layer.tips(
        [
          '<div class="layui-table-tips-main" style="margin-top: -' +
            (elemCell.height() + 23) +
            'px;' +
            (function () {
              if (options.size === 'sm') {
                return 'padding: 4px 15px; font-size: 12px;';
              }
              if (options.size === 'lg') {
                return 'padding: 14px 15px;';
              }
              return '';
            })() +
            '">',
          elemCell.html(),
          '</div>',
          '<i class="layui-icon layui-table-tips-c layui-icon-close"></i>',
        ].join(''),
        elemCell[0],
        {
          tips: [3, ''],
          time: -1,
          anim: -1,
          maxWidth: device.ios || device.android ? 300 : that.elem.width() / 2,
          isOutAnim: false,
          skin: 'layui-table-tips',
          success: function (layero, index) {
            layero.find('.layui-table-tips-c').on('click', function () {
              layer.close(index);
            });
          },
        },
      );
    } else {
      // 多行展开风格
      // 恢复其他已经展开的单元格
      that.elem.find('.' + ELEM_CELL_C).trigger('click');

      // 设置当前单元格展开宽度
      that.cssRules(key, function (item) {
        var width = item.style.width;
        var expandedWidth = col.expandedWidth || options.cellExpandedWidth;

        // 展开后的宽度不能小于当前宽度
        if (expandedWidth < parseFloat(width))
          expandedWidth = parseFloat(width);

        elemCellClose.data('cell-width', width);
        item.style.width = expandedWidth + 'px';

        setTimeout(function () {
          that.scrollPatch(); // 滚动条补丁
        });
      });

      // 设置当前单元格展开样式
      that.setRowActive(index, ELEM_EXPAND);

      // 插入关闭按钮
      if (!elemCell.next('.' + ELEM_CELL_C)[0]) {
        elemCell.after(elemCellClose);
      }

      // 关闭展开状态
      elemCellClose.on('click', function () {
        var $this = $(this);
        that.setRowActive(index, [ELEM_EXPAND, ELEM_HOVER].join(' '), true); // 移除单元格展开样式
        that.cssRules(key, function (item) {
          item.style.width = $this.data('cell-width'); // 恢复单元格展开前的宽度
          setTimeout(function () {
            that.resize(); // 滚动条补丁
          });
        });
        $this.remove();
        // 重置单元格滚动条位置
        elemCell.scrollTop(0);
        elemCell.scrollLeft(0);
      });
    }

    othis.remove();
    layui.stope(e);
  };

  // 表格主体单元格展开事件
  that.layBody.on('click', '.' + ELEM_GRID_DOWN, function (e) {
    gridExpand.call(this, e);
  });
  // 表格合计栏单元格展开事件
  that.layTotal.on('click', '.' + ELEM_GRID_DOWN, function (e) {
    gridExpand.call(this, e, 'tips'); // 强制采用 tips 风格
  });

  // 行工具条操作事件
  var toolFn = function (type) {
    var othis = $(this);
    var td = othis.closest('td');
    var index = othis.parents('tr').eq(0).data('index');
    // 标记当前活动行
    that.setRowActive(index);

    // 执行事件
    layui.event.call(
      this,
      MOD_NAME,
      (type || 'tool') + '(' + filter + ')',
      commonMember.call(this, {
        event: othis.attr('lay-event'),
        getCol: function () {
          // 获取当前列的表头配置信息
          return that.col(td.data('key'));
        },
      }),
    );
  };

  // 行工具条单击事件
  that.layBody
    .on('click', '*[lay-event]', function (e) {
      toolFn.call(this);
      layui.stope(e);
    })
    .on('dblclick', '*[lay-event]', function (e) {
      //行工具条双击事件
      toolFn.call(this, 'toolDouble');
      layui.stope(e);
    });

  // 同步滚动条
  that.layMain.on('scroll', function () {
    var othis = $(this);
    var scrollLeft = othis.scrollLeft();
    var scrollTop = othis.scrollTop();

    that.layHeader.scrollLeft(scrollLeft);
    that.layTotal.scrollLeft(scrollLeft);
    that.layFixed.find(ELEM_BODY).scrollTop(scrollTop);

    layer.close(that.tipsIndex);
  });

  var rAF =
    window.requestAnimationFrame ||
    function (fn) {
      return setTimeout(fn, 1000 / 60);
    };

  // 固定列滚轮事件 - 临时兼容方案
  that.layFixed.find(ELEM_BODY).on('mousewheel DOMMouseScroll', function (e) {
    var delta = e.originalEvent.wheelDelta || -e.originalEvent.detail;
    var scrollTop = that.layMain.scrollTop();
    var step = 100;
    var rAFStep = 10;

    e.preventDefault();
    var cb = function () {
      if (step > 0) {
        step -= rAFStep;
        scrollTop += delta > 0 ? -rAFStep : rAFStep;
        that.layMain.scrollTop(scrollTop);
        rAF(cb);
      }
    };
    rAF(cb);
  });
};

/**
 * 获取元素的大小
 * @param {HTMLElement} elem - HTML 元素
 */
Class.prototype.getElementSize = function (elem) {
  if (!window.getComputedStyle) return;

  var style = window.getComputedStyle(elem, null);
  return {
    height: parseFloat(style.height || '0'),
    width: parseFloat(style.width || '0'),
    borderTopWidth: parseFloat(style.borderTopWidth || '0'),
    borderRightWidth: parseFloat(style.borderRightWidth || '0'),
    borderBottomWidth: parseFloat(style.borderBottomWidth || '0'),
    borderLeftWidth: parseFloat(style.borderLeftWidth || '0'),
    paddingTop: parseFloat(style.paddingTop || '0'),
    paddingRight: parseFloat(style.paddingRight || '0'),
    paddingBottom: parseFloat(style.paddingBottom || '0'),
    paddingLeft: parseFloat(style.paddingLeft || '0'),
    marginTop: parseFloat(style.marginTop || '0'),
    marginRight: parseFloat(style.marginRight || '0'),
    marginBottom: parseFloat(style.marginBottom || '0'),
    marginLeft: parseFloat(style.marginLeft || '0'),
    boxSizing: style.boxSizing,
  };
};

/**
 * 获取元素 content 区域宽度值
 *
 * layui 内置 jQuery v1.12.4 中的 jQuery.fn.width 始终对值四舍五入(3.x 已修复),
 * 在支持 subpixel Rendering 的浏览器中渲染表格，由于列宽分配时计算值精度不足，
 * 可能会导致一些小问题(#1726)
 *
 * 这个方法使用 getComputedStyle 获取精确的宽度值进行计算，为了尽可能和以前的行为
 * 保持一致(主要是隐藏元素内渲染 table 递归获取父元素宽度 https://github.com/layui/layui/discussions/2398)，
 * 任何非预期的值，都回退到 jQuery.fn.width。未来的版本使用 ResizeObserver 时，可以直接获取表格视图元素的宽度，
 * 并移除兼容性代码
 *
 * @param {JQuery} elem - 元素的 jQuery 对象
 *
 * @see {@link https://learn.microsoft.com/zh-cn/archive/blogs/ie_cn/css-3}
 */
Class.prototype.getContentWidth = function (elem) {
  var that = this;

  if (
    // document
    elem[0].nodeType === 9 ||
    // IE 中 border-box 盒模型，getComputedStyle 得到的 width/height 是按照 content-box 计算出来的
    (lay.ie && elem.css('box-sizing') === 'border-box') ||
    elem.css('display') === 'none'
  ) {
    return elem.width();
  }

  var size = that.getElementSize(elem[0]);

  // display: none|inline 元素，getComputedStyle 无法得到准确的 width/height
  if (typeof size === 'undefined' || !size.width) {
    return elem.width();
  } else {
    return size.boxSizing === 'border-box'
      ? size.width -
          size.paddingLeft -
          size.paddingRight -
          size.borderLeftWidth -
          size.borderRightWidth
      : size.width;
  }
};

Class.prototype.dispose = function () {
  var that = this;

  that.unobserveResize();
  for (var propName in that) {
    if (lay.hasOwn(that, propName) && propName !== 'config') {
      that[propName] = null;
    }
  }
};

Class.prototype.calcFixedRowHeight = function () {
  var that = this;

  var tableElem = that.layMain.children('table');
  var leftTrs = that.layFixLeft.find('>.layui-table-body>table>tbody>tr');
  var rightTrs = that.layFixRight.find('>.layui-table-body>table>tbody>tr');
  var mainTrs = tableElem.find('>tbody>tr');

  // 批量获取主表格行高，设置高度以优化性能
  var heights = [];
  mainTrs.each(function () {
    heights.push(that.getElementSize(this).height);
  });

  if (leftTrs.length) {
    leftTrs.each(function (i) {
      if (heights[i]) {
        this.style.height = heights[i] + 'px';
      }
    });
  }

  if (rightTrs.length) {
    rightTrs.each(function (i) {
      if (heights[i]) {
        this.style.height = heights[i] + 'px';
      }
    });
  }
};

// 鼠标悬停于某一列纵向移动时，若单元格会出现横向滚动条，此时 tbody 高度不变，需要修复行高
Class.prototype.fixedRowHeightPatchOnHover = function (
  targetEl,
  trsElem,
  isEnter,
) {
  var that = this;
  var style = that.elem.children('style')[0];
  var selector = '.' + FIXED_HEIGHT_PATCH;

  trsElem.toggleClass(FIXED_HEIGHT_PATCH, isEnter);
  // 将当前鼠标悬停行的高度同步到 FIXED_HEIGHT_PATCH （所有区域）类名的样式中
  if (isEnter) {
    lay.getStyleRules(style, function (item) {
      if (item.selectorText === selector) {
        item.style.setProperty(
          'height',
          that.getElementSize(targetEl).height + 'px',
          'important',
        );
      }
    });
  } else {
    // 将当前鼠标悬停行主区域的行高同步到固定列行
    var height;
    lay.getStyleRules(style, function (item) {
      if (item.selectorText === selector) {
        item.style.setProperty('height', 'auto');
      }
    });
    trsElem = trsElem.filter(function () {
      var tr = $(this);
      var isFixed = tr.closest(ELEM_FIXED, that.layBox).length > 0;
      if (!isFixed) {
        height = that.getElementSize(tr[0]).height;
      }
      return isFixed;
    });

    trsElem.css('height', height);
  }
};

Class.prototype.observeResize = function () {
  var that = this;

  if (!resizeObserver) return;

  that.unobserveResize();

  var el = that.elem[0];
  var tableEl = that.layMain.children('table')[0];

  // 显示或隐藏时重置列宽
  resizeObserver.observe(el, that.resize.bind(that));

  // 同步固定列表格和主表格高度
  var lineStyle = that.config.lineStyle;
  var isAutoHeight = lineStyle && /\bheight\s*:\s*auto\b/g.test(lineStyle);

  // 只重载数据时需要主动同步高度，因为 tbody 大小可能不变
  var needSyncFixedRowHeight = (that.needSyncFixedRowHeight =
    that.layBody.length > 1 &&
    (that.config.syncFixedRowHeight ||
      (that.config.syncFixedRowHeight !== false && isAutoHeight)));

  if (needSyncFixedRowHeight) {
    resizeObserver.observe(tableEl, that.calcFixedRowHeight.bind(that));
  }

  that.unobserveResize = function () {
    resizeObserver.unobserve(el);
    if (needSyncFixedRowHeight) {
      resizeObserver.unobserve(tableEl);
    }

    that.unobserveResize = $.noop;
  };
};

// 全局事件
(function () {
  // 自适应尺寸
  _WIN.on('resize', function () {
    layui.each(thisTable.that, function () {
      this.resize();
    });
  });

  // 全局点击
  _DOC.on('click', function () {
    _DOC.trigger('table.remove.tool.panel');
  });

  // 工具面板移除事件
  _DOC.on('table.remove.tool.panel', function () {
    $('.' + ELEM_TOOL_PANEL).remove();
  });
})();

// 初始化
table.init = function (filter, settings) {
  settings = settings || {};
  var that = this;
  // var inst = null;
  var elemTable =
    typeof filter === 'object'
      ? filter
      : typeof filter === 'string'
        ? $('table[lay-filter="' + filter + '"]')
        : $(ELEM + '[lay-data], ' + ELEM + '[lay-options]');
  var errorTips =
    'Table element property lay-data configuration item has a syntax error: ';

  //遍历数据表格
  elemTable.each(function () {
    var othis = $(this);
    var attrData = othis.attr('lay-data');
    var tableData = lay.options(this, {
      attr: attrData ? 'lay-data' : null,
      errorText: errorTips + (attrData || othis.attr('lay-options')),
    });

    var options = $.extend(
      {
        elem: this,
        cols: [],
        data: [],
        skin: othis.attr('lay-skin'), //风格
        size: othis.attr('lay-size'), //尺寸
        even: typeof othis.attr('lay-even') === 'string', //偶数行背景
      },
      table.config,
      settings,
      tableData,
    );

    filter && othis.hide();

    //获取表头数据
    othis.find('thead>tr').each(function (i) {
      options.cols[i] = [];
      $(this)
        .children()
        .each(function () {
          var th = $(this);
          var attrData = th.attr('lay-data');
          var itemData = lay.options(this, {
            attr: attrData ? 'lay-data' : null,
            errorText: errorTips + (attrData || th.attr('lay-options')),
          });

          var row = $.extend(
            {
              title: th.text(),
              colspan: parseInt(th.attr('colspan')) || 1, //列单元格
              rowspan: parseInt(th.attr('rowspan')) || 1, //行单元格
            },
            itemData,
          );

          options.cols[i].push(row);
        });
    });

    //缓存静态表体数据
    var trElem = othis.find('tbody>tr');

    //执行渲染
    var tableIns = table.render(options);

    //获取表体数据
    if (trElem.length && !settings.data && !tableIns.config.url) {
      var tdIndex = 0;
      table.eachCols(tableIns.config.id, function (i3, item3) {
        trElem.each(function (i1) {
          options.data[i1] = options.data[i1] || {};
          var tr = $(this);
          var field = item3.field;
          options.data[i1][field] = tr.children('td').eq(tdIndex).html();
        });
        tdIndex++;
      });

      tableIns.reloadData({
        data: options.data,
      });
    }
  });

  return that;
};

//记录所有实例
thisTable.that = {}; //记录所有实例对象
thisTable.config = {}; //记录所有实例配置项

var eachChildCols = function (index, cols, i1, item2) {
  //如果是组合列，则捕获对应的子列
  if (item2.colGroup) {
    var childIndex = 0;
    index++;
    item2.CHILD_COLS = [];
    // 找到它的子列所在cols的下标
    var i2 = i1 + (parseInt(item2.rowspan) || 1);
    layui.each(cols[i2], function (i22, item22) {
      if (item22.parentKey) {
        // 如果字段信息中包含了parentKey和key信息
        if (item22.parentKey === item2.key) {
          item22.PARENT_COL_INDEX = index;
          item2.CHILD_COLS.push(item22);
          eachChildCols(index, cols, i2, item22);
        }
      } else {
        // 没有key信息以colspan数量所谓判断标准
        //如果子列已经被标注为{PARENT_COL_INDEX}，或者子列累计 colspan 数等于父列定义的 colspan，则跳出当前子列循环
        if (
          item22.PARENT_COL_INDEX ||
          (childIndex >= 1 && childIndex == (item2.colspan || 1))
        )
          return;
        item22.PARENT_COL_INDEX = index;
        item2.CHILD_COLS.push(item22);
        childIndex =
          childIndex + parseInt(item22.colspan > 1 ? item22.colspan : 1);
        eachChildCols(index, cols, i2, item22);
      }
    });
  }
};

// 遍历表头
table.eachCols = function (id, callback, cols) {
  var config = thisTable.config[id] || {};
  var arrs = [],
    index = 0;

  cols = $.extend(true, [], cols || config.cols);

  //重新整理表头结构
  layui.each(cols, function (i1, item1) {
    if (i1) return true; // 只需遍历第一层
    layui.each(item1, function (i2, item2) {
      eachChildCols(index, cols, i1, item2);
      if (item2.PARENT_COL_INDEX) return; //如果是子列，则不进行追加，因为已经存储在父列中
      arrs.push(item2);
    });
  });

  //重新遍历列，如果有子列，则进入递归
  var eachArrs = function (obj) {
    layui.each(obj || arrs, function (i, item) {
      if (item.CHILD_COLS) return eachArrs(item.CHILD_COLS);
      typeof callback === 'function' && callback(i, item);
    });
  };

  eachArrs();
};

// 获取表格选中状态
table.checkStatus = function (id) {
  var invalidNum = 0;
  var arr = [];
  var dataCache = [];
  var data = table.cache[id] || [];

  // 过滤禁用或已删除的数据
  layui.each(data, function (i, item) {
    if (layui.type(item) === 'array' || item[table.config.disabledName]) {
      invalidNum++; // 无效数据数量
      return;
    }
    if (item[table.config.checkName]) {
      arr.push(table.clearCacheKey(item));
      dataCache.push(item);
    }
  });

  return {
    data: arr, // 选中的数据
    dataCache: dataCache, // 选中的原始缓存数据，包含内部特定字段
    isAll:
      data.length && arr.length
        ? arr.length === data.length - invalidNum
        : false, // 是否全选
  };
};

// 设置行选中状态
table.setRowChecked = function (id, opts) {
  var that = getThisTable(id);
  if (!that) return;
  that.setRowChecked(opts);
};

// 获取表格当前页的所有行数据
table.getData = function (id) {
  var arr = [];
  var data = table.cache[id] || [];
  layui.each(data, function (i, item) {
    if (layui.type(item) === 'array') {
      return;
    }
    arr.push(table.clearCacheKey(item));
  });
  return arr;
};

// 重置表格尺寸结构
table.resize = function (id) {
  // 若指定表格唯一 id，则只执行该 id 对应的表格实例
  if (id) {
    var config = getThisTableConfig(id); // 获取当前实例配置项
    if (!config) return;

    getThisTable(id).resize();
  } else {
    // 否则重置所有表格实例尺寸
    layui.each(thisTable.that, function () {
      this.resize();
    });
  }
};

// 表格导出
table.exportFile = function (id, data, opts) {
  data = data || table.clearCacheKey(table.cache[id]);
  opts =
    typeof opts === 'object'
      ? opts
      : (function () {
          var obj = {};
          opts && (obj.type = opts);
          return obj;
        })();

  var type = opts.type || 'csv';
  var thatTable = thisTable.that[id];
  var config = thisTable.config[id] || {};
  var textType = {
    csv: 'text/csv',
    xls: 'application/vnd.ms-excel',
  }[type];
  var alink = document.createElement('a');

  if (device.ie) return hint.error('IE_NOT_SUPPORT_EXPORTS');

  // 处理 treeTable 数据
  var isTreeTable = config.tree && config.tree.view;
  if (isTreeTable) {
    try {
      data = $.extend(true, [], table.cache[id]);
      data = (function fn(data) {
        return data.reduce(function (acc, obj) {
          var children = obj.children || [];
          delete obj.children;
          return acc.concat(obj, fn(children));
        }, []);
      })(Array.from(data));
    } catch {
      // ignore
    }
  }

  alink.href =
    'data:' +
    textType +
    ';charset=utf-8,\ufeff' +
    encodeURIComponent(
      (function () {
        var dataTitle = [];
        var dataMain = [];
        var dataTotal = [];
        var fieldsIsHide = {};

        // 表头和表体
        layui.each(data, function (i1, item1) {
          var vals = [];
          if (typeof id === 'object') {
            // 若 id 参数直接为表头数据
            layui.each(id, function (i, item) {
              i1 == 0 && dataTitle.push(item || '');
            });
            layui.each(
              layui.isArray(item1)
                ? $.extend([], item1)
                : table.clearCacheKey(item1),
              function (i2, item2) {
                vals.push('"' + (item2 || '') + '"');
              },
            );
          } else {
            table.eachCols(id, function (i3, item3) {
              if (
                item3.ignoreExport === false ||
                (item3.field && item3.type == 'normal')
              ) {
                // 不导出隐藏列，除非设置 ignoreExport 强制导出
                if (
                  (item3.hide && item3.ignoreExport !== false) ||
                  item3.ignoreExport === true // 忽略导出
                ) {
                  if (i1 == 0) fieldsIsHide[item3.field] = true; // 记录隐藏列
                  return;
                }

                var content = item1[item3.field];
                if (content === undefined || content === null) content = '';

                i1 == 0 &&
                  dataTitle.push(
                    item3.fieldTitle || item3.title || item3.field || '',
                  );

                // 解析内容
                content = parseTempData.call(thatTable, {
                  item3: item3,
                  content: content,
                  tplData: item1,
                  text: 'text',
                  obj: {
                    td: function (field) {
                      if (isTreeTable) i1 = item1['LAY_DATA_INDEX']; // 兼容 treeTable 索引
                      var td = thatTable.layBody.find(
                        'tr[data-index="' + i1 + '"]>td',
                      );
                      return td.filter('[data-field="' + field + '"]');
                    },
                  },
                });

                // 异常处理
                content = content.replace(/"/g, '""'); // 避免内容存在「双引号」导致异常分隔
                // content += '\t'; // 加「水平制表符」 避免内容被转换格式
                content = '"' + content + '"'; // 避免内容存在「逗号」导致异常分隔

                // 插入内容
                vals.push(content);
              } else if (item3.field && item3.type !== 'normal') {
                // https://gitee.com/layui/layui/issues/I8PHCR
                if (i1 == 0) fieldsIsHide[item3.field] = true;
              }
            });
          }
          dataMain.push(vals.join(','));
        });

        // 表合计
        thatTable &&
          layui.each(thatTable.dataTotal, function (i, o) {
            fieldsIsHide[o.field] ||
              dataTotal.push('"' + (o.total || '') + '"');
          });

        return (
          dataTitle.join(',') +
          '\r\n' +
          dataMain.join('\r\n') +
          '\r\n' +
          dataTotal.join(',')
        );
      })(),
    );

  alink.download =
    (opts.title || config.title || 'table_' + (config.index || '')) +
    '.' +
    type;
  document.body.appendChild(alink);
  alink.click();
  document.body.removeChild(alink);
};

// 获取表格配置信息
table.getOptions = function (id) {
  return getThisTableConfig(id);
};

// 显示或隐藏列
table.hideCol = function (id, cols) {
  var that = getThisTable(id);
  if (!that) {
    return;
  }

  if (layui.type(cols) === 'boolean') {
    // 显示全部或者隐藏全部
    that.eachCols(function (i2, item2) {
      var key = item2.key;
      var col = that.col(key);
      var parentKey = item2.parentKey;
      // 同步勾选列的 hide 值和隐藏样式
      if (col.hide != cols) {
        var hide = (col.hide = cols);
        that.elem
          .find('*[data-key="' + key + '"]')
          [hide ? 'addClass' : 'removeClass'](HIDE);
        // 根据列的显示隐藏，同步多级表头的父级相关属性值
        that.setParentCol(hide, parentKey);
      }
    });
  } else {
    cols = layui.isArray(cols) ? cols : [cols];
    layui.each(cols, function (i1, item1) {
      that.eachCols(function (i2, item2) {
        if (item1.field === item2.field) {
          var key = item2.key;
          var col = that.col(key);
          var parentKey = item2.parentKey;
          // 同步勾选列的 hide 值和隐藏样式
          if ('hide' in item1 && col.hide != item1.hide) {
            var hide = (col.hide = !!item1.hide);
            that.elem
              .find('*[data-key="' + key + '"]')
              [hide ? 'addClass' : 'removeClass'](HIDE);
            // 根据列的显示隐藏，同步多级表头的父级相关属性值
            that.setParentCol(hide, parentKey);
          }
        }
      });
    });
  }
  $('.' + ELEM_TOOL_PANEL).remove(); // 关闭字段筛选面板如果打开的话
  // 重新适配尺寸
  that.resize();
};

// 重载
table.reload = function (id, options, deep, type) {
  var config = getThisTableConfig(id); //获取当前实例配置项
  if (!config) return;

  var that = getThisTable(id);
  that.reload(options, deep, type);

  return thisTable.call(that);
};

// 仅重载数据
table.reloadData = function () {
  var args = $.extend([], arguments);
  args[3] = 'reloadData';

  // 重载时，影响整个结构的参数，不适合更新的参数
  var dataParams = new RegExp(
    '^(' +
      [
        'elem',
        'id',
        'cols',
        'width',
        'height',
        'maxHeight',
        'toolbar',
        'defaultToolbar',
        'className',
        'css',
        'pagebar',
      ].join('|') +
      ')$',
  );

  // 过滤与数据无关的参数
  layui.each(args[1], function (key) {
    if (dataParams.test(key)) {
      delete args[1][key];
    }
  });

  return table.reload.apply(null, args);
};

// 核心入口
table.render = function (options) {
  var inst = new Class(options);
  return thisTable.call(inst);
};

// 清除临时 Key
table.clearCacheKey = function (data) {
  data = $.extend({}, data);
  delete data[table.config.checkName];
  delete data[table.config.indexName];
  delete data[table.config.initIndexName];
  delete data[table.config.numbersName];
  delete data[table.config.disabledName];
  return data;
};

// 自动完成渲染
$(function () {
  table.init();
});

export { table };
